Docker 演示教程:一次打包,到处运行
面向初学者的 Docker 实操教程,10 分钟演示镜像与容器概念、Dockerfile 构建、Docker Compose 多服务编排、常用排错命令,并通过 Node.js + nginx + PostgreSQL 博客项目实战完成容器化部署。
视频讲解
Docker 演示教程:一次打包,到处运行
开场 Hook
Screen
黑底白字大字幕快速闪现:Mac、Windows、Linux 三台电脑屏幕依次出现"环境报错"的红字。镜头切到终端,敲下 docker run nginx,3 秒后浏览器里跑出网站。三台电脑同时显示成功页面——画面定格。
Narration
Docker 是全球开发者都在用的容器化工具。它解决了一个老问题:在你电脑上能跑,到别人电脑上就报错。开发、测试、部署,用同一套环境。Docker 不难,但概念容易混。今天十分钟,让你从零到能上手。咱们直接开干。
第一章 — Docker 到底是什么
本章引入
Screen
打开浏览器,访问 https://hub.docker.com,滚动到 nginx 镜像页面。回到终端,准备敲命令。
Narration
学 Docker,第一件事就是搞懂两个词:镜像和容器。我打个比方——镜像就是饼干模具,容器就是用模具扣出来的饼干。一个模具可以扣无数块饼干,一个镜像可以启动无数个容器。模具不能改,饼干用完就扔。这两件事搞明白,后面的命令都是顺水推舟。
Demo 1.1:拉取并运行一个镜像
Screen
打开 iTerm(macOS)/ Terminal(Linux)/ PowerShell(Windows)。输入 docker --version 确认安装。
Command
docker --version
docker pull nginx
docker images
Output
Docker version 24.0.7, build afdd53b
Using default tag: latest
latest: Pulling from library/nginx
a2abf6c4d29d: Pull complete
...
Status: Downloaded newer image for nginx:latest
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest a2abf6c4d29d 2 weeks ago 187MB
Narration
先看一眼 Docker 装上没有。然后 docker pull nginx,从官方仓库拉一个 nginx 镜像下来。注意看进度条——一层一层下载,跟 Git 提交一层一层很像。docker images 看本地有什么镜像。这就是我们的"模具",还只是文件,没跑起来。
Demo 1.2:用镜像启动一个容器
Screen
终端输入启动命令,浏览器切到 http://localhost:8080。
Command
docker run -d --name my-web -p 8080:80 nginx
docker ps
Output
a1b2c3d4e5f6... nginx:latest "/docker-entrypoint.…" 2 seconds ago Up 1 second 0.0.0.0:8080->80/tcp my-web
CONTAINER ID IMAGE COMMAND STATUS PORTS NAMES
a1b2c3d4e5f6 nginx "/docker-entrypoint.…" Up 3 seconds 0.0.0.0:8080->80/tcp my-web
Narration
docker run 就是用模具扣饼干。-d 让它在后台跑,--name my-web 给它起个名字,-p 8080:80 把主机的 8080 端口映射到容器的 80 端口。docker ps 看哪些容器在跑。现在切到浏览器,访问 localhost:8080——nginx 欢迎页出来了。三条命令,一个网站跑起来,不需要装 nginx、不需要配环境,这就是 Docker。
Demo 1.3:停止并删除容器
Screen
终端操作,浏览器刷新页面看效果。
Command
docker stop my-web
docker ps -a
docker rm my-web
docker run -d --name my-web-2 -p 8080:80 nginx
curl -I http://localhost:8080
Output
my-web
CONTAINER ID IMAGE STATUS NAMES
a1b2c3d4e5f6 nginx Exited (0) 3 seconds ago my-web
my-web
a2b3c4d5e6f7... nginx:latest Up 1 second 0.0.0.0:8080->80/tcp my-web-2
HTTP/1.1 200 OK
Server: nginx/1.25.3
...
Narration
容器是临时的。停掉、删掉,随时可以再扣一块。"容器坏了就删掉重建,不要修它"——这是黄金法则。修出来的东西没法复现,叫"雪花服务器"。
本章回顾
Key Takeaway
镜像 = 模具(只读模板),容器 = 用模具做出的产品(运行中的实例)。一个镜像可以启动无数个容器,容器删除重建只需一两秒。
Practical Use Case
任何需要"环境一致"的场景都用得上:开发环境、CI/CD、测试环境、生产部署。今天用的 nginx 镜像就是 Docker Hub 上的现成模具,5 亿+ 次下载——绝大多数中间件都有人帮你做好了。
Transition
镜像跑起来了,但怎么跑自己的应用?下一章,我们写 Dockerfile,构建自己的镜像。
第二章 — 写 Dockerfile,构建你的第一个镜像
本章引入
Screen
新建一个项目文件夹,打开 VSCode。
Narration
光跑别人的镜像没意思。我们要跑自己的应用。Dockerfile 就是菜谱,告诉 Docker 怎么一步步做出你需要的镜像。每条指令是一步操作,写好菜谱,Docker 负责烹饪。
Demo 2.1:创建项目结构
Screen
VSCode 新建文件夹 hello-docker,打开终端。
Command
mkdir hello-docker && cd hello-docker
touch index.js Dockerfile .dockerignore
Output
hello-docker/
├── .dockerignore
├── Dockerfile
└── index.js
Narration
项目根目录建三个文件:index.js 是应用代码,Dockerfile 是菜谱,.dockerignore 排除不需要的文件。结构简单,咱们一行一行写。
Demo 2.2:写应用代码
Screen
VSCode 打开 index.js,输入代码。
Command
const http = require("http");
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
res.end("Hello Docker! 这是我的第一个容器化应用。\n");
});
server.listen(3000, () => {
console.log("Server running on http://localhost:3000");
});
Output
(文件保存,无终端输出)
Narration
一个最简单的 Node.js HTTP 服务器,监听 3000 端口,返回一句话。咱们在本地不跑它——直接在 Docker 里跑。
Demo 2.3:写 Dockerfile
Screen
VSCode 打开 Dockerfile,逐行输入。
Command
FROM node:20-alpine
WORKDIR /app
COPY package.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "index.js"]
Output
(文件保存,无终端输出)
Narration
每行都是一个步骤:FROM 选基础镜像——这里用 alpine 版本,只有 120MB,比默认镜像小 6 倍。WORKDIR 设工作目录。COPY package.json 先拷依赖清单,RUN npm install 装依赖——这一步很关键。COPY . . 最后再拷代码。这样改代码时,依赖这步可以复用缓存,构建从 5 秒搞定,不用每次都装一遍。EXPOSE 声明端口,CMD 启动命令。
Demo 2.4:写 .dockerignore
Screen
VSCode 打开 .dockerignore 文件。
Command
node_modules
.git
.env
*.log
Output
(文件保存,无终端输出)
Narration
.dockerignore 是给 Docker 的"黑名单"。node_modules 本机装的不算数,容器里要重装;.git、.env 不应该进镜像。这一步让构建更快、镜像更小、更安全。
Demo 2.5:补一个 package.json
Screen
VSCode 新建 package.json 文件。
Command
{
"name": "hello-docker",
"version": "1.0.0",
"main": "index.js"
}
Output
(文件保存,无终端输出)
Narration
因为我们 COPY package.json 了,这个文件得存在。没有依赖没关系——npm install 会很快跑完。但有了它,将来加依赖也方便。
Demo 2.6:构建镜像
Screen
VSCode 终端,定位到项目目录,执行 build。
Command
docker build -t hello-docker:v1 .
Output
[+] Building 15.2s (10/10) FINISHED
=> [internal] load build definition from Dockerfile
=> [internal] load .dockerignore
=> [1/5] FROM docker.io/library/node:20-alpine
=> [2/5] WORKDIR /app
=> [3/5] COPY package.json ./
=> [4/5] RUN npm install
=> [5/5] COPY . .
=> exporting to image
=> => naming to docker.io/library/hello-docker:v1
Narration
docker build 是按菜谱做菜的过程。-t hello-docker:v1 给镜像起名打标签,方便以后引用。结尾的 . 表示"当前目录找 Dockerfile"。每一步的输出对应 Dockerfile 里的一行。15 秒构建完成。
Demo 2.7:运行容器并测试
Screen
终端启动容器,切到浏览器。
Command
docker run -d -p 3000:3000 --name hello-app hello-docker:v1
docker logs hello-app
curl http://localhost:3000
Output
b2c3d4e5f6a7...
Server running on http://localhost:3000
Hello Docker! 这是我的第一个容器化应用。
Narration
启动命令跟第一章很像,只是换了端口和镜像名。docker logs 看容器在说什么——"Server running on http://localhost:3000"。`curl` 测一下,浏览器也访问一下——我们的应用跑起来了。代码、环境、依赖,全都封装在这个叫 hello-docker:v1 的模具里。换台机器,docker run 一行命令,立刻就能跑。
Demo 2.8:验证镜像可移植性
Screen
终端查看镜像信息,导出再导入试一下。
Command
docker images hello-docker
docker history hello-docker:v1
docker stop hello-app && docker rm hello-app
Output
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-docker v1 f8e7d6c5b4a3 2 min ago 145MB
IMAGE CREATED CREATED BY SIZE
f8e7d6c5b4a3 2 minutes ago CMD ["node" "index.js"] 0B
<missing> 2 minutes ago COPY . . 4B
<missing> 3 minutes ago RUN npm install 18MB
...
Narration
docker history 看镜像每一层是什么——每层对应 Dockerfile 一行。这就是 Dockerfile 的核心价值:你写的每一行,最终都变成了镜像里的一层,改动可追溯,构建可复现。
本章回顾
Key Takeaway
Dockerfile 是菜谱,镜像按菜谱做,容器用镜像启动。 关键技巧:先拷依赖清单、装依赖,最后才拷代码——这样改代码不会触发依赖重装。
Practical Use Case
任何后端服务(Node、Python、Go、Java)都可以这样打包。Dockerfile 进 Git,团队任何人、任何机器 docker build 出来的镜像都是一致的。
Transition
单个容器跑起来了。但真实项目往往是多个服务:前端、后端、数据库。下一章,我们用 Docker Compose 一键启动整个应用。
第三章 — Docker Compose:多容器编排
本章引入
Screen
打开浏览器,访问 Docker 官方文档的 Compose 页面。回到 VSCode,新建 multi-service-demo 文件夹。
Narration
真实项目至少三件套:前端、后端、数据库。纯用命令启动——创建网络、等数据库就绪、起后端、起前端——敲一堆命令,容易搞错顺序。Docker Compose 让所有服务写在一个文件里,一条命令全启动。这个文件进 Git,换台电脑一键还原。
Demo 3.1:搭建项目骨架
Screen
VSCode 新建目录,创建三个子目录。
Command
mkdir multi-service-demo && cd multi-service-demo
mkdir web api db
touch docker-compose.yml
Output
multi-service-demo/
├── api/
├── db/
├── docker-compose.yml
└── web/
Narration
三个目录代表三个服务。docker-compose.yml 是入口文件,统一描述整个应用。
Demo 3.2:写后端 API
Screen
VSCode 打开 api/server.js,输入代码。
Command
const http = require("http");
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({
message: "Hello from API",
time: new Date().toISOString()
}));
});
server.listen(4000, () => console.log("API on :4000"));
Output
(文件保存,无终端输出)
Narration
API 服务跑在 4000 端口,返回 JSON。注意:接下来在 Compose 文件里我们直接用 api 这个服务名连它,不写 IP。
Demo 3.3:写前端页面
Screen
VSCode 打开 web/index.html。
Command
<!DOCTYPE html>
<html>
<body>
<h1>Multi-Service Demo</h1>
<div id="out">Loading...</div>
<script>
fetch("http://localhost:4000/")
.then(r => r.json())
.then(d => document.getElementById("out").innerText =
JSON.stringify(d));
</script>
</body>
</html>
Output
(文件保存,无终端输出)
Narration
前端通过 localhost:4000 调后端(开发阶段浏览器直连,生产环境会换种方式)。
Demo 3.4:写 docker-compose.yml
Screen
VSCode 打开 docker-compose.yml,逐行输入。
Command
services:
api:
image: node:20-alpine
working_dir: /app
volumes:
- ./api:/app
command: sh -c "npm init -y >/dev/null && npm install -g http-proxy && node server.js"
ports:
- "4000:4000"
web:
image: nginx:alpine
volumes:
- ./web:/usr/share/nginx/html:ro
ports:
- "8080:80"
depends_on:
- api
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: mypass
POSTGRES_DB: appdb
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Output
(文件保存,无终端输出)
Narration
这是 Compose 的核心——一个文件,三服务。api 用 node 镜像跑后端;web 用 nginx 镜像托管前端静态文件;db 用 postgres 镜像跑数据库。三个关键点:volumes 把本地目录挂进容器(改代码不用重建);depends_on 控制启动顺序;最下方的 volumes: pgdata 给数据库做持久化——容器删了数据还在。
Demo 3.5:一键启动所有服务
Screen
终端执行 docker compose up -d,切换到浏览器。
Command
docker compose up -d
docker compose ps
sleep 3 && curl -s http://localhost:4000/
Output
[+] Running 4/4
✔ Network multi-service-demo_default Created
✔ Volume multi-service-demo_pgdata Created
✔ Container multi-service-demo-api-1 Started
✔ Container multi-service-demo-web-1 Started
✔ Container multi-service-demo-db-1 Started
NAME SERVICE STATUS PORTS
multi-service-demo-api-1 api Up 5 seconds 0.0.0.0:4000->4000/tcp
multi-service-demo-web-1 web Up 4 seconds 0.0.0.0:8080->80/tcp
multi-service-demo-db-1 db Up 4 seconds 5432/tcp
{"message":"Hello from API","time":"2026-07-06T10:23:45.123Z"}
Narration
一条 docker compose up -d,三个容器全起来。docker compose ps 看每个服务的状态。curl 测一下后端——返回 JSON。前端访问 localhost:8080,浏览器会调用 4000 的 API,看到 API 的输出。这就是多服务编排。
Demo 3.6:服务名就是地址
Screen
终端进入 API 容器,用服务名连数据库。
Command
docker compose exec api sh
# 在容器内
apk add --no-cache postgresql-client >/dev/null && \
psql -h db -U postgres -d appdb -c "SELECT version();"
exit
Output
# 进入容器后
/app # psql -h db -U postgres -d appdb -c "SELECT version();"
version
PostgreSQL 16.2 on x86_64-pc-linux-musl, compiled by gcc...
Narration
看仔细——我们连数据库用的是 -h db,不是 IP。Compose 自动给所有服务做了 DNS,在同一个网络里,服务名就是主机名。这就是 Compose 帮你省掉的功夫——不用记 IP、不用配 /etc/hosts。
Demo 3.7:查看日志和停止
Screen
终端操作演示日志查看和一键停止。
Command
docker compose logs --tail 10 api
docker compose logs -f db &
sleep 2 && kill %1 2>/dev/null
docker compose down
docker compose ps
docker compose down -v
Output
api-1 | Server running on :4000
db-1 | PostgreSQL init process complete; ready for start up.
NAME SERVICE STATUS PORTS
Removing network multi-service-demo_default
Removing volume multi-service-demo_pgdata
Narration
docker compose logs 看日志,不用记容器全名。docker compose down 停服务、删容器、删网络。注意——这不会删数据卷。数据卷要单独用 -v 才删,平时千万别加,否则数据库就没了。
本章回顾
Key Takeaway
Compose 让多服务用一个 YAML 文件描述,一条命令启停。 同一网络里服务名就是主机名;数据库一定要加 volume,否则容器删了数据全没。
Practical Use Case
本地开发环境:前端 + 后端 + 数据库 + Redis + 消息队列,全装进 Compose。CI/CD 测试环境:每个 PR 启动一组完整服务跑测试,跑完销毁。
Transition
服务跑起来了,总会遇到问题——容器启动就退出、连不上数据库。下一章,我们讲常用命令和排错技巧。
第四章 — 常用命令与排错技巧
本章引入
Screen
终端准备操作,浏览器打开 Docker 官方命令参考。
Narration
容器不出问题是不可能的。掌握三个命令能解决 80% 的问题:docker ps -a 看状态、docker logs 看日志、docker exec -it 进容器。配合常用命令速查表,大部分问题自己就能搞定。
Demo 4.1:排错三步法演示
Screen
终端模拟一个"启动失败"的场景。
Command
# 故意写错入口文件
docker run -d --name broken-app hello-docker:v1
sleep 2
docker ps -a
docker logs broken-app
docker rm -f broken-app
Output
CONTAINER ID IMAGE STATUS NAMES
b1c2d3e4f5a6 hello-docker:v1 Exited (1) 2 seconds ago broken-app
docker: Error response from daemon: failed to create task ...:
invalid working directory "/app": directory not found
# 因为当前目录没 COPY . .? 故意测试
Narration
容器启动后立刻退出——STATUS 显示 Exited (1)。docker logs 看一眼,立刻告诉你"工作目录不存在"或者"找不到模块"。日志就是答案,这就是为什么"先看日志"是排错的第一步。
Demo 4.2:进入容器内部调试
Screen
启动一个 alpine 容器并进入交互式 shell。
Command
docker run -d --name debug-demo alpine sleep 3600
docker exec -it debug-demo sh
# 在容器内
ls /
cat /etc/os-release
exit
Output
/ # ls /
bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
/ # cat /etc/os-release
NAME="Alpine Linux"
VERSION_ID=3.19.4
...
Narration
docker exec -it 像 SSH 进服务器。-i 保持输入,-t 分配终端。进去之后 ls、cat、ps,跟普通 Linux 命令一模一样。但记住——这是调试用,不要在容器里手动改东西然后保存。改了配置要改 Dockerfile,重新 build。
Demo 4.3:清理无用资源
Screen
终端演示清理命令。
Command
docker system df
docker system prune -f
docker image prune -f
docker volume prune -f
Output
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 8 3 1.2GB 800MB (66%)
Containers 5 2 50MB 30MB (60%)
Local Volumes 3 1 200MB 150MB (75%)
Build Cache 15 0 500MB 500MB (100%)
Deleted Containers:
...
Total reclaimed space: 1.4GB
Narration
构建几次后,docker images 里全是 <none>:<none> 的废弃镜像。docker system prune 一键清理——停掉的容器、无用的网络、悬空镜像、构建缓存。注意:它不会删正在用的容器和数据卷。
Demo 4.4:常用命令速查演练
Screen
终端逐条演示速查表命令。
Command
docker ps # 运行中的容器
docker ps -a # 全部容器(含已停)
docker logs --tail 20 web # 容器最后 20 行日志
docker stop web # 停止容器(发送 SIGTERM)
docker kill web # 强制停止(SIGKILL)
docker rm web # 删除已停的容器
docker rm -f web # 强制删除(运行中也可)
docker images # 镜像列表
docker rmi nginx:latest # 删除镜像
docker volume ls # 数据卷列表
docker network ls # 网络列表
Output
CONTAINER ID IMAGE STATUS PORTS NAMES
(具体输出按当前环境而定)
REPOSITORY TAG IMAGE ID CREATED SIZE
Narration
这张表打印出来贴显示器旁边。前五条 ps / logs / exec / stop / rm,加上 prune,足够应付日常 90% 的场景。
本章回顾
Key Takeaway
排错三步法:ps 看状态 → logs 看日志 → exec 进容器。 容器是临时的,配置改 Dockerfile,不在容器里手动保存。
Practical Use Case
CI/CD 跑测试:测试容器失败,立刻 docker logs 看错误;本地开发:服务起不来,先看 STATUS 是 Exited 还是 Restarting,再决定看哪里的日志。
Transition
四章基础都讲完了。咱们来一个真实的综合实战——把今天所有东西串起来。
最终实战项目 — 用 Docker Compose 启动一个真实博客应用
Screen
终端新建项目目录 blog-stack,全程在 VSCode 里操作。
Narration
最后来一个完整的实战:搭一个博客系统,包含 Node 后端 + nginx 前端 + postgres 数据库,全部用 Docker Compose 编排,5 分钟跑起来。
Demo 5.1:搭建项目骨架
Screen
VSCode 新建项目目录,创建子目录结构。
Command
mkdir blog-stack && cd blog-stack
mkdir -p api web
touch docker-compose.yml api/package.json
Output
blog-stack/
├── api/
│ └── package.json
├── docker-compose.yml
└── web/
Narration
项目结构简单:一个 docker-compose.yml,一个后端目录,一个前端目录。咱们从后端开始。
Demo 5.2:写后端 + 自己的 Dockerfile
Screen
VSCode 创建 api/server.js、api/Dockerfile。
Command
// api/server.js
const http = require("http");
const { Client } = require("pg");
const client = new Client({
host: process.env.DB_HOST || "db",
user: "postgres",
password: "mypass",
database: "blogdb"
});
async function start() {
await client.connect();
await client.query(`
CREATE TABLE IF NOT EXISTS posts (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
)
`);
await client.query(
"INSERT INTO posts (title) VALUES ($1) ON CONFLICT DO NOTHING",
["Welcome to Docker!"]
);
http.createServer(async (req, res) => {
res.writeHead(200, { "Content-Type": "application/json" });
const { rows } = await client.query(
"SELECT id, title, created_at FROM posts ORDER BY id DESC"
);
res.end(JSON.stringify(rows));
}).listen(4000, () => console.log("Blog API on :4000"));
}
start().catch(e => { console.error(e); process.exit(1); });
Command
// api/package.json
{
"name": "blog-api",
"version": "1.0.0",
"main": "server.js",
"dependencies": { "pg": "^8.11.0" }
}
Command
# api/Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package.json .
RUN npm install --production
COPY server.js .
EXPOSE 4000
CMD ["node", "server.js"]
Output
(文件保存,无终端输出)
Narration
后端连数据库(用服务名 db)、自动建表、插入演示数据、暴露 JSON API。注意 Dockerfile 还是那个套路:先拷 package.json,再 npm install,最后拷代码。
Demo 5.3:写前端页面
Screen
VSCode 打开 web/index.html。
Command
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>Blog</title></head>
<body>
<h1>📝 My Docker Blog</h1>
<ul id="posts"></ul>
<script>
fetch("/api/posts")
.then(r => r.json())
.then(posts => {
document.getElementById("posts").innerHTML =
posts.map(p =>
`<li><b>${p.title}</b> — ${new Date(p.created_at).toLocaleString()}</li>`
).join("");
});
</script>
</body>
</html>
Output
(文件保存,无终端输出)
Narration
前端通过相对路径 /api/posts 调后端——nginx 接下来会做反向代理。
Demo 5.4:写 docker-compose.yml
Screen
VSCode 打开 docker-compose.yml,输入完整编排。
Command
services:
api:
build: ./api
environment:
DB_HOST: db
depends_on:
- db
web:
image: nginx:alpine
volumes:
- ./web:/usr/share/nginx/html:ro
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
ports:
- "8080:80"
depends_on:
- api
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: mypass
POSTGRES_DB: blogdb
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Output
(文件保存,无终端输出)
Narration
三个服务:api 自己 build(用 api/Dockerfile),web 用官方 nginx 镜像,db 用 postgres。depends_on 控制顺序。api 用环境变量 DB_HOST=db 连数据库。
Demo 5.5:写 nginx 反向代理配置
Screen
VSCode 新建 nginx.conf。
Command
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html;
}
location /api/ {
proxy_pass http://api:4000/;
}
}
Output
(文件保存,无终端输出)
Narration
nginx 把 /api/ 路径代理到 api 服务。看到没——http://api:4000/——这就是 Compose 的 DNS 魔法:服务名当主机名用,零配置。
Demo 5.6:一键启动
Screen
终端执行启动命令,等待几秒后测试。
Command
docker compose up -d --build
docker compose ps
sleep 5
curl -s http://localhost:8080/api/posts
curl -s http://localhost:8080/ | head -20
Output
[+] Running 4/4
✔ Network blog-stack_default Created
✔ Volume blog-stack_pgdata Created
✔ Container blog-stack-api-1 Started
✔ Container blog-stack-web-1 Started
✔ Container blog-stack-db-1 Started
NAME SERVICE STATUS PORTS
blog-stack-api-1 api Up 8 seconds
blog-stack-web-1 web Up 7 seconds 0.0.0.0:8080->80/tcp
blog-stack-db-1 db Up 7 seconds 5432/tcp
[{"id":1,"title":"Welcome to Docker!","created_at":"2026-07-06T10:30:00.000Z"}]
<!DOCTYPE html>
<html>
<head>...
Narration
docker compose up -d --build——既构建又启动。三服务起来后,curl localhost:8080/api/posts 拿到数据库里的文章;curl localhost:8080/ 拿到前端页面。打开浏览器访问 localhost:8080——看到博客首页,标题"Welcone to Docker!"显示出来了。
Demo 5.7:验证持久化
Screen
终端演示重启数据不丢。
Command
# 在数据库里手动插入一条数据
docker compose exec db psql -U postgres -d blogdb \
-c "INSERT INTO posts (title) VALUES ('数据持久化测试');"
# 删掉整个栈
docker compose down
# 重启
docker compose up -d
sleep 5
curl -s http://localhost:8080/api/posts
Output
INSERT 0 1
[{"id":2,"title":"数据持久化测试","created_at":"..."},
{"id":1,"title":"Welcome to Docker!","created_at":"..."}]
Narration
手插一条数据,docker compose down 删容器,重启——数据还在。这就是 volumes: pgdata 的价值——容器是临时的,卷是持久的。如果上一章没加 volume,这一步数据就没了。
Demo 5.8:清理
Screen
终端操作,演示收尾。
Command
docker compose down # 停服务、删容器、保留数据
docker compose down -v # 删数据卷(演示用)
docker compose down --rmi all # 连镜像一起删
Output
[+] Running 4/0
✔ Container blog-stack-api-1 Removed
✔ Container blog-stack-web-1 Removed
✔ Container blog-stack-db-1 Removed
✔ Network blog-stack_default Removed
Narration
三种收尾方式:down 保留卷,down -v 连数据一起删(演示完用),down --rmi all 把自定义镜像也清掉。日常开发:down 即可;想完全重置:down -v。
课程收尾
Screen
回到终端黑屏,文字浮现课程总结。
Narration
十分钟到了。咱们一路干了这些事:
- 理解了镜像 vs 容器——模具 vs 饼干
- 写了第一个 Dockerfile,构建了自己的镜像
- 用 Docker Compose 编排多服务应用
- 掌握了排错三步法:ps、logs、exec
- 最后跑了一个完整的博客系统:前端 + 后端 + 数据库,三服务联通
你现在能做什么:把任何 Node、Python、Go、Java 项目 Docker 化;用 Compose 拉起一套完整本地开发环境;遇到问题自己排。
下一步学什么:
- 多阶段构建,让生产镜像更小
- Docker Compose 高级用法(健康检查、自动重启)
- 容器编排:Swarm 或 Kubernetes
- CI/CD:把镜像推到 Docker Hub,用 GitHub Actions 自动部署
Docker 不难,难的是"看不看日志"。只要遇到问题先看日志,大部分都能自己解决。代码写得再好,不会 Docker 也只能在自己的机器上跑——现在你可以让它跑到世界上任何一台机器上。
去试一下,把你现在正在做的项目 Docker 化。下一期见。
速查附录(可直接复制到收藏夹)
镜像与容器
docker pull nginx # 拉镜像
docker images # 看镜像
docker rmi nginx # 删镜像
docker run -d -p 8080:80 --name web nginx # 跑容器
docker ps / docker ps -a # 看容器
docker stop web / docker rm web # 停/删
docker logs -f web # 跟日志
docker exec -it web sh # 进容器
Dockerfile 模板
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
EXPOSE 3000
CMD ["node", "index.js"]
Compose 模板
services:
app:
build: .
ports: ["3000:3000"]
depends_on: [db]
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: mypass
volumes: [pgdata:/var/lib/postgresql/data]
volumes:
pgdata:
排错流程
docker ps -a # 1. 状态?
docker logs app # 2. 日志?
docker exec -it app sh # 3. 进容器看
docker system prune # 4. 清理无用资源
实践示例
拉取并运行 nginx 镜像
编写 Dockerfile 构建自己的 Node.js 应用
使用 Docker Compose 一键启动前端、后端、数据库三服务
通过 docker logs 和 exec 排查容器问题
构建一个包含 Node 后端、nginx 前端和 PostgreSQL 数据库的博客应用
后续与前置知识
- Git 基础教程:10 分钟从零开始掌握版本控制相关
面向零基础初学者的 Git 实操教程,10 分钟演示 init、add、commit、push、pull 等核心命令,覆盖身份配置、仓库初始化、暂存与提交、历史查看、分支合并、冲突解决、远程 GitHub 推送与克隆及最佳实践。
- GitHub 演示教程:从零跑通协作全流程后续
10 分钟带你完成一次完整的 GitHub 协作闭环:仓库创建、分支与 PR、Issue 追踪、Actions 自动测试,覆盖 GitHub Flow、Pull Request 审查、Issues 任务管理以及 GitHub Actions 自动化 CI/CD。
- Node.js 演示教程:从“这东西跟我有关系?”到写完一个全栈应用相关
10 分钟 Node.js 实操教程,从识别电脑上已存在的 Node.js 开始,对比 npm 与 pnpm 的安装速度差异,使用内置 http 模块在 30 秒内启动一个 Web 服务器,再进阶用 pnpm + Express 在 60 行内完成一个支持增删改查的 Todo API,并演示 --watch、EADDRINUSE 报错处理以及 async/await 异步写法三个新手常见坑。
引用此知识点
- FastAPI 接口开发 演示教程:从 Hello World 到一个完整 CRUD API后续
本文通过十个演示和最终实战,使用 FastAPI 官方教程风格,从三行代码的 Hello World 开始,逐步讲解路径参数、查询参数、请求体、Pydantic 校验、自动文档、异常处理、枚举类型、PUT/DELETE,并最终实现一个基于内存字典的完整 CRUD API,全程使用 TestClient 测试,无需启动服务器。
- 命令行演示教程:10 分钟从图形界面走向键盘自由后续
面向零基础初学者的命令行实操教程,10 分钟从打开终端开始,讲解 Shell、Terminal、PATH 核心概念,演示文件系统操作、管道重定向与文本处理,并编写 Shell 脚本完成日志分析报告生成器。
- 数据库导论:12 分钟看懂整个数据库世界相关
面向零基础学习者的数据库导论教程,12分钟建立数据库世界地图,覆盖为何需要数据库、OLTP vs OLAP、9类数据库系统(关系型、键值、文档、宽列、图、搜索、时序、OLAP、向量)、SQL vs NoSQL、共同框架(Schema、数据类型、CRUD、持久化、索引)、对象存储与数据库的区别,以及程序如何通过 Driver、Connection Pool、ORM 连接数据库。
- 文档数据库入门:MongoDB 与 Couchbase相关
本教程系统讲解文档数据库核心概念,以 MongoDB 和 Couchbase 为代表,涵盖数据模型、CRUD 操作、查询与聚合、索引、副本集、分片、事务、多模型能力、选型对比及 Python 接入方法,帮助建立文档数据库全景认知。
- 键值数据库入门:Redis 与 DynamoDB相关
本教程系统讲解键值数据库核心概念,以 Redis 和 DynamoDB 为代表,对比内存与云托管两种形态,涵盖 Redis 五种数据结构、过期持久化高可用、经典场景,DynamoDB 数据模型、主键设计、CRUD、二级索引、容量一致性、单表设计,并通过 Python 示例连接两者,最后给出选型建议与练习。
- 分析型数据库入门:ClickHouse、DuckDB、Snowflake、BigQuery后续
本教程系统讲解分析型数据库(OLAP)核心概念,以 ClickHouse、DuckDB、Snowflake、BigQuery 为代表,涵盖列式存储、向量化执行、存算分离与无服务器架构,对比四者选型,并演示 Python 连接与适用场景,帮助快速建立分析型数据库全景认知。
- 搜索数据库入门:Elasticsearch 与 OpenSearch前置
本教程系统讲解搜索数据库核心概念,以 Elasticsearch 与 OpenSearch 为代表,涵盖倒排索引、集群与索引/分片/副本数据模型、REST API 全文搜索、分词与 BM25 相关性排序、聚合分析、两者对比及 Python 接入方法,帮助建立搜索数据库全景认知。
- SQL 入门:从一张表看懂数据库相关
本文从一张表入手,系统讲解 SQL 数据库核心概念与操作,涵盖建库查数据、列记录主键、约束索引、DDL/DML/DQL/DCL/TCL 五类 SQL 语言、SELECT 聚合分组排序、JOIN 多表拼接、五大范式与反范式、事务与 ACID、视图索引权限、SQLite/PostgreSQL/MySQL 对比、Python 连接数据库、ORM 映射以及 DBeaver 图形化管理,适合快速建立 SQL 全景。
- 时序数据库入门:InfluxDB 与 TimescaleDB相关
本文系统讲解时序数据库核心概念,以 InfluxDB 和 TimescaleDB 为代表,涵盖数据模型、Line Protocol 与 Flux/InfluxQL 查询、保留策略与降采样、超表与连续聚合/压缩、两者对比及 Python 接入方法,帮助建立时序数据库全景认知。
- 宽列数据库入门:Cassandra 与 ScyllaDB前置
本教程系统讲解宽列数据库核心概念,以 Cassandra 和 ScyllaDB 为代表,涵盖数据模型(分区键+列族)、CQL 与 CRUD、无主架构与可调一致性、LSM 树与建模、ScyllaDB 的 Shard-per-core 架构、两者对比、Python 连接及适用场景,帮助快速建立宽列数据库全景认知。