前言

Docker 是当今最流行的容器化技术。它让”在我机器上能跑啊“成为历史——将应用及其依赖打包成一个标准化的容器,在任何地方都能一致地运行。

本文将从零开始,带你系统掌握 Docker 的核心操作。

为什么需要 Docker?

痛点 Docker 解决方案
“我电脑上能跑,服务器不行” 环境一致:容器内环境完全相同
安装 MySQL、Redis 太麻烦 一行命令跑起来,用完就删
开发环境不一致 共享镜像,团队统一环境
微服务部署复杂 Docker Compose 一键编排
资源利用率低 轻量级,比虚拟机省资源

核心概念

概念 类比 说明
镜像(Image) 安装光盘 / 类模板 包含运行环境 + 应用的只读模板
容器(Container) 运行中的实例 镜像的运行实例,可启动/停止/删除
仓库(Registry) App Store 存储和分发镜像(如 Docker Hub)
Dockerfile 配方/菜谱 描述如何构建镜像的文本文件
Docker Compose 编排工具 定义和管理多容器应用

一、环境搭建

1.1 安装 Docker

1
2
3
4
5
6
7
# Ubuntu
sudo apt update
sudo apt install docker.io docker-compose-v2
sudo usermod -aG docker $USER # 免 sudo 使用(需重新登录)

# 启动 Docker
sudo systemctl enable docker --now

1.2 验证安装

1
2
3
4
5
6
7
8
docker --version
# Docker version 28.0.0, build ...

docker compose version
# Docker Compose version v2.32.0

docker run hello-world
# Hello from Docker! (如果看到这个,安装成功 ✅)

二、镜像管理

2.1 搜索镜像

1
2
3
# 在 Docker Hub 搜索镜像
docker search nginx
docker search mysql --limit 5

2.2 拉取镜像

1
2
3
4
docker pull nginx                  # 拉取最新版本
docker pull nginx:1.25 # 拉取指定版本
docker pull mysql:8.0
docker pull python:3.12-alpine # 基于 Alpine 的精简版

2.3 查看本地镜像

1
2
3
docker images
# 或者
docker image ls

输出示例:

1
2
3
4
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
nginx latest 2b7d6430f78d 2 weeks ago 188MB
python 3.12-slim 8f1b5a10c8aa 3 weeks ago 152MB
hello-world latest d2c94e258dcb 8 months ago 13.3kB

2.4 查看镜像详情

1
2
docker inspect nginx              # 查看镜像详细信息(JSON 格式)
docker history nginx # 查看镜像构建历史(层)

2.5 删除镜像

1
2
3
4
docker rmi nginx                  # 按名称删除
docker rmi 2b7d6430f78d # 按 ID 删除
docker image prune # 删除所有未使用的镜像
docker image prune -a # 删除所有未被容器使用的镜像

三、容器操作(最常用)⭐

3.1 运行容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 基本运行
docker run nginx

# 后台运行 + 端口映射 + 命名
docker run -d -p 8080:80 --name my-nginx nginx

# 参数说明:
# -d 后台运行(detached)
# -p 8080:80 端口映射:宿主机端口:容器端口
# --name 给容器取个名字
# -it 交互模式(用于需要输入的命令行)
# --rm 容器停止后自动删除
# -v 挂载数据卷
# -e 设置环境变量
# --restart 重启策略(如 always、unless-stopped)

实战:一行命令启动 MySQL

1
2
3
4
5
6
7
8
docker run -d \
--name mysql-blog \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=my-secret-pw \
-e MYSQL_DATABASE=my_blog \
-v mysql-data:/var/lib/mysql \
--restart unless-stopped \
mysql:8.0

打开另一个终端测试:

1
2
# 进入 MySQL 容器执行命令
docker exec -it mysql-blog mysql -u root -p

3.2 查看容器

1
2
3
4
5
docker ps                        # 查看运行中的容器
docker ps -a # 查看所有容器(包括已停止的)
docker ps -a -q # 只显示容器 ID
docker stats # 查看容器资源使用情况(CPU/内存)
docker top my-nginx # 查看容器内进程

docker ps 输出解读:

1
2
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS                  NAMES
a1b2c3d4e5f6 nginx "/docker-entrypoint.…" 2 minutes ago Up 2 minutes 0.0.0.0:8080->80/tcp my-nginx
说明
CONTAINER ID 容器唯一标识
IMAGE 使用的镜像
STATUS 运行状态(Up=运行中, Exited=已停止)
PORTS 端口映射关系

3.3 查看容器日志

1
2
3
4
docker logs my-nginx               # 查看全部日志
docker logs --tail 50 my-nginx # 最后 50 行
docker logs -f my-nginx # 实时跟踪日志(Ctrl+C 退出)
docker logs --since 1h my-nginx # 最近 1 小时的日志

3.4 进入容器内部

1
2
3
4
5
6
7
8
9
# 进入正在运行的容器
docker exec -it my-nginx /bin/bash

# 对于没有 bash 的精简镜像(如 Alpine),用 sh
docker exec -it my-nginx /bin/sh

# 在里面可以像普通 Linux 一样操作
ls /etc/nginx/
cat /etc/nginx/nginx.conf

3.5 停止、启动、重启

1
2
3
4
5
6
docker stop my-nginx               # 优雅停止(SIGTERM → SIGKILL)
docker stop -t 30 my-nginx # 等待 30 秒再强制停止
docker start my-nginx # 启动已停止的容器
docker restart my-nginx # 重启容器
docker pause my-nginx # 暂停容器进程
docker unpause my-nginx # 恢复暂停的容器

3.6 删除容器

1
2
3
4
docker rm my-nginx                 # 删除已停止的容器
docker rm -f my-nginx # 强制删除(即使正在运行)
docker rm $(docker ps -aq) # 删除所有容器(危险!)
docker container prune # 删除所有已停止的容器

3.7 容器与宿主机之间文件拷贝

1
2
3
4
5
# 从宿主机复制到容器
docker cp ./index.html my-nginx:/usr/share/nginx/html/

# 从容器复制到宿主机
docker cp my-nginx:/etc/nginx/nginx.conf ./nginx.conf.bak

四、数据卷(Volume)— 持久化数据

容器删除后,容器内的数据也会丢失。数据卷用于持久化数据。

4.1 命名卷(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建数据卷
docker volume create blog-data

# 使用时挂载
docker run -d \
--name mysql \
-v blog-data:/var/lib/mysql \
mysql:8.0

# 管理数据卷
docker volume ls # 列出所有卷
docker volume inspect blog-data # 查看卷详情
docker volume rm blog-data # 删除卷
docker volume prune # 删除未使用的卷

4.2 绑定挂载(开发环境常用)

将宿主机目录直接映射到容器内,实时同步

1
2
3
4
5
6
# 将当前目录挂载到容器的 /app
docker run -d \
-p 3000:3000 \
-v $(pwd):/app \
-v /app/node_modules \ # 匿名卷,保留容器内的 node_modules
node:20 node /app/index.js

💡 绑定挂载非常适合开发环境——修改代码后容器内自动生效,无需重新构建。

4.3 数据卷对比

方式 适用场景 示例
命名卷 数据库、持久化数据 -v mysql-data:/var/lib/mysql
绑定挂载 开发环境、配置文件 -v ./src:/app/src
tmpfs 临时缓存(内存中) --tmpfs /tmp

五、Dockerfile — 构建自定义镜像

5.1 第一个 Dockerfile

以 Node.js 项目为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# Dockerfile
# ===== 阶段 1:构建阶段 =====
FROM node:20-alpine AS builder

WORKDIR /app

# 先复制依赖文件(利用缓存层)
COPY package.json package-lock.json ./
RUN npm ci --production

# 复制源码并构建
COPY . .
RUN npm run build

# ===== 阶段 2:运行阶段 =====
FROM node:20-alpine

WORKDIR /app

# 创建非 root 用户(安全最佳实践)
RUN addgroup -g 1001 app && \
adduser -u 1001 -G app -s /bin/sh -D app

# 从构建阶段复制产物
COPY --from=builder /app/package.json /app/package-lock.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist

# 切换到非 root 用户
USER app

EXPOSE 3000

ENV NODE_ENV=production

CMD ["node", "dist/main.js"]

5.2 Dockerfile 常用指令

指令 说明 示例
FROM 基础镜像 FROM python:3.12
WORKDIR 设置工作目录 WORKDIR /app
COPY 复制文件 COPY . /app
ADD 复制文件(支持 URL 和自动解压 tar) ADD app.tar.gz /app
RUN 构建时执行命令 RUN apt install -y curl
CMD 容器启动时的默认命令 CMD ["python", "app.py"]
ENTRYPOINT 容器入口点(不能被 docker run 后的命令覆盖) ENTRYPOINT ["nginx"]
ENV 设置环境变量 ENV NODE_ENV=production
EXPOSE 声明端口(文档作用,实际还需 -p 映射) EXPOSE 80
VOLUME 声明挂载点 VOLUME /data
USER 切换运行用户 USER app
ARG 构建参数 ARG VERSION=1.0

5.3 CMD vs ENTRYPOINT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# CMD:默认命令,docker run 后面跟的命令会覆盖它
CMD ["echo", "hello"]
# docker run my-image → 输出 hello
# docker run my-image world → 输出 world

# ENTRYPOINT:固定入口,后面的参数追加到它后面
ENTRYPOINT ["echo"]
# docker run my-image hello → 输出 hello
# docker run my-image world → 输出 world

# 两者结合:ENTRYPOINT 定义程序,CMD 定义默认参数
ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8000"]
# docker run my-image → python app.py --port 8000
# docker run my-image --port 3000 → python app.py --port 3000

5.4 构建镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 基本构建
docker build -t my-app:1.0 .

# 指定 Dockerfile
docker build -f Dockerfile.prod -t my-app:prod .

# 使用构建参数
docker build --build-arg VERSION=2.0 -t my-app:2.0 .

# 不使用缓存(完全重新构建)
docker build --no-cache -t my-app .

# 构建后查看
docker images my-app

5.5 多阶段构建的优势

  • 🪶 镜像体积小:只保留最终运行需要的文件
  • 🔒 更安全:不包含编译工具、SDK 等构建依赖
  • 📉 减少攻击面:只安装运行时依赖

5.6 .dockerignore

类似于 .gitignore,排除不需要的文件,加快构建速度:

1
2
3
4
5
6
7
node_modules
.git
*.md
.DS_Store
dist
.env
.env.local

六、Docker Compose — 多容器编排

当项目需要多个服务(如 Web + MySQL + Redis),Compose 可以一键管理。

6.1 第一个 docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# docker-compose.yml
version: "3.8"

services:
# Web 应用
web:
build: .
container_name: my-blog-web
ports:
- "3000:3000"
environment:
- DB_HOST=db
- DB_USER=root
- DB_PASSWORD=${DB_PASSWORD} # 从 .env 文件读取
- REDIS_URL=redis://redis:6379
volumes:
- ./src:/app/src # 开发热重载
- /app/node_modules
depends_on:
db:
condition: service_healthy # 等待数据库就绪
redis:
condition: service_started
restart: unless-stopped
networks:
- blog-network

# MySQL 数据库
db:
image: mysql:8.0
container_name: my-blog-db
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
MYSQL_DATABASE: my_blog
volumes:
- db-data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql # 初始化脚本
ports:
- "3306:3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- blog-network

# Redis 缓存
redis:
image: redis:7-alpine
container_name: my-blog-redis
volumes:
- redis-data:/data
command: redis-server --appendonly yes
restart: unless-stopped
networks:
- blog-network

# Nginx 反向代理
nginx:
image: nginx:alpine
container_name: my-blog-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- web
restart: unless-stopped
networks:
- blog-network

volumes:
db-data:
redis-data:

networks:
blog-network:
driver: bridge

6.2 Compose 常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 启动所有服务(后台运行)
docker compose up -d

# 启动并重新构建
docker compose up -d --build

# 查看日志
docker compose logs
docker compose logs -f web # 只看 web 服务的实时日志
docker compose logs --tail=100

# 查看服务状态
docker compose ps
docker compose top

# 停止服务
docker compose stop

# 停止并删除容器、网络
docker compose down

# 停止并删除容器、网络、数据卷
docker compose down -v

# 重启单个服务
docker compose restart web

# 进入某个服务容器
docker compose exec web /bin/sh

# 在服务容器中执行命令
docker compose exec db mysql -u root -p

# 列出所有 compose 项目
docker compose ls

6.3 .env 文件

1
2
3
4
# .env(用于 docker-compose.yml 中的变量)
DB_PASSWORD=my-secret-password
NODE_ENV=production
DOMAIN=blog.iot2045.cn

6.4 depends_on 与健康检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 简单的依赖(只等待容器启动,不保证服务就绪)
depends_on:
- db

# 严格依赖(等待健康检查通过)
depends_on:
db:
condition: service_healthy

# 配合健康检查
services:
db:
image: mysql:8.0
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s

七、Docker 网络

7.1 网络类型

网络类型 说明
bridge 默认网络,容器间通过 IP 通信
host 直接使用宿主机网络
none 无网络
overlay 跨主机(Swarm 模式)

7.2 网络命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查看网络
docker network ls

# 创建自定义网络
docker network create my-network

# 查看网络详情(有哪些容器连在上面)
docker network inspect my-network

# 将运行中的容器加入网络
docker network connect my-network my-container

# 断开
docker network disconnect my-network my-container

# 删除网络
docker network rm my-network

7.3 Compose 中的网络

docker-compose.yml 中,同一 Compose 文件的服务自动属于同一网络,可以通过服务名互相访问:

1
2
3
4
5
6
7
8
9
services:
web:
# ...
environment:
- DB_HOST=db # 直接用服务名,不需要 IP!
- DB_PORT=3306

db:
# ...

八、实战:完整的博客项目 Docker 化

8.1 项目结构

1
2
3
4
5
6
7
8
9
10
my-blog/
├── docker-compose.yml
├── Dockerfile
├── .dockerignore
├── .env
├── nginx/
│ └── nginx.conf
├── init.sql # 数据库初始化
└── app/ # 应用代码
└── ...

8.2 Nginx 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# nginx/nginx.conf
events {
worker_connections 1024;
}

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

# 日志格式(包含请求耗时)
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time';

access_log /var/log/nginx/access.log main;

# 上游服务器
upstream web_backend {
server web:3000;
}

# HTTP → HTTPS 重定向
server {
listen 80;
server_name blog.iot2045.cn;
return 301 https://$server_name$request_uri;
}

# HTTPS
server {
listen 443 ssl http2;
server_name blog.iot2045.cn;

ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;

# 静态文件缓存
location /static/ {
root /var/www/html;
expires 30d;
add_header Cache-Control "public, immutable";
}

# 反向代理到后端
location / {
proxy_pass http://web_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}

8.3 数据库初始化脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- init.sql(挂载到 /docker-entrypoint-initdb.d/,容器首次启动时自动执行)
CREATE TABLE IF NOT EXISTS articles (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
content LONGTEXT,
views INT DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

INSERT IGNORE INTO users (username, password) VALUES
('admin', '$2y$10$...预先生成的哈希...');

8.4 启动项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 克隆项目
git clone https://gitee.com/yourname/my-blog.git
cd my-blog

# 复制环境变量文件
cp .env.example .env
# 编辑 .env,设置数据库密码等

# 一键启动
docker compose up -d

# 查看启动状态
docker compose ps
docker compose logs -f

# 等待 mysql healthy 后,访问 http://localhost
curl -I http://localhost

8.5 日常维护命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 查看日志
docker compose logs -f web

# 重启某个服务
docker compose restart web

# 应用代码更新后重新构建
git pull
docker compose up -d --build

# 数据库备份
docker compose exec db mysqldump -u root -p my_blog > backup.sql

# 数据库恢复
docker compose exec -T db mysql -u root -p my_blog < backup.sql

# 查看磁盘使用
docker system df
docker system df -v # 详细版

九、优化技巧

9.1 减小镜像体积

1
2
3
4
5
6
7
8
9
10
11
12
13
# ❌ 体积大
FROM ubuntu:22.04
RUN apt update && apt install -y python3 python3-pip

# ✅ 体积小
FROM python:3.12-alpine # Alpine 只有 ~5MB

# ✅ 精简运行依赖
FROM python:3.12-slim # 比完整版小很多

# ✅ 清理缓存
RUN apt update && apt install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*

9.2 利用构建缓存

1
2
3
4
5
6
7
8
9
# ❌ 每次代码变更都重新安装依赖
COPY . .
RUN npm install

# ✅ 利用 Docker 层缓存:先拷贝依赖文件,再装包
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
# 代码变更时只重新执行最后两行

9.3 镜像分层策略

变化频率最低的层放在前面:

1
2
基础系统依赖 → 语言运行时 → 依赖安装 → 应用代码
(几乎不变) (偶尔变) (比较常变) (最常变)

十、常用命令速查表

分类 命令 说明
镜像 docker pull image:tag 拉取镜像
镜像 docker images 查看本地镜像
镜像 docker rmi image 删除镜像
镜像 docker build -t name . 构建镜像
容器 docker run -d -p 80:80 --name n nginx 启动容器
容器 docker ps 查看运行中的容器
容器 docker ps -a 查看所有容器
容器 docker stop / start / restart 停/启/重启
容器 docker rm container 删除容器
容器 docker logs -f container 查看日志
容器 docker exec -it container sh 进入容器
容器 docker cp a.txt container:/path 复制文件
系统 docker system prune -a 清理所有无用资源
系统 docker system df 查看磁盘占用
系统 docker stats 资源监控
编排 docker compose up -d 启动服务
编排 docker compose down 停止并删除
编排 docker compose ps 查看服务状态
编排 docker compose logs -f 查看日志
编排 docker compose exec svc sh 进入服务容器

清理命令大全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 清理所有停止的容器、未使用的网络、悬挂镜像
docker system prune

# 清理所有未使用的资源(包括未使用的镜像)
docker system prune -a

# 只清理停止的容器
docker container prune

# 只清理未使用的镜像
docker image prune -a

# 只清理未使用的数据卷
docker volume prune

# 查看磁盘占用
docker system df

十一、部署检查清单

部署到生产环境前,请确认:

  • 使用多阶段构建减小镜像体积
  • 不要以 root 用户运行容器(USER 指令)
  • 敏感信息通过环境变量Secrets 传入,不硬编码在镜像中
  • 数据库密码等使用 .env 文件,且 .env 加入 .gitignore
  • 使用健康检查确保服务就绪
  • 设置资源限制(mem_limitcpus
  • 配置日志轮转,避免日志撑满磁盘
  • 端口只暴露必要的(数据库端口不对外)
  • 使用 --restart unless-stopped 确保崩溃自动重启
  • 数据库数据务必挂载数据卷

进一步学习

  1. Docker Swarm / Kubernetes:容器编排,大规模集群管理
  2. CI/CD 集成:GitHub Actions + Docker 自动构建部署
  3. 镜像仓库:搭建私有 Registry(如 Harbor)
  4. Docker 网络深入:overlay、macvlan 等高级网络
  5. 安全扫描:Trivy、Docker Scout 扫描镜像漏洞

结语

Docker 是现代软件开发的标配技能。它解决了”环境不一致”这一头号痛点,让开发、测试、部署变得简单可控。

本文从零开始,覆盖了 Docker 的核心概念、镜像管理、容器操作、Dockerfile 编写、Compose 编排和实践项目。掌握了这些,你已经可以:

  • ✅ 一键启动 MySQL / Redis / Nginx 等开发环境
  • ✅ 将现有项目 Docker 化
  • ✅ 用 Compose 编排多服务应用
  • ✅ 编写生产级别的 Dockerfile

Build once, run anywhere. 🐳