docker常见使用技巧

1. 区别

1.1 多阶段构建

你可以在一个 Dockerfile 中定义多个构建阶段。例如,一个阶段用来编译代码(包含所有开发依赖),另一个阶段只把编译好的产物复制到一个干净的基础镜像中。

1.2 CMD Vs. ENTRYPOINT:容器的 “ 使命 “ 与 “ 参数 “

  • 想象一下,你的容器是一个 “ 命令行工具 “。
  • ENTRYPOINT : 定义了这个工具的 “ 程序本身 “ 。它是固定的,是容器的核心使命。比如,这个工具就是 git 。
  • CMD : 定义了这个工具的 “ 默认参数 “ 。如果用户不指定任何参数,就用这个。比如, git 的默认参数是 –help
  • CMD 被覆盖,ENTRYPOINT 被用来传参数用
1
2
3
4
# 怎么都会执行 `docker-entrypoint.sh`,你可以选择覆盖 cmd

ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["redis-server"]

1.3 Volume Vs. Bind Mount

这两种都是将数据持久化到容器之外的方法,但管理方式和适用场景截然不同。

  • Bind Mount (绑定挂载) : 像是你已有的 “ 私家别墅 “ 。你告诉 Docker:” 把我的这个文件夹(别墅)直接给容器用 “。你对这个文件夹有完全的控制权。
    • 将主机上的文件或目录直接挂载到容器中
    • ✅ 更好的可移植性
  • Volume (数据卷) : 像是 Docker 为你管理的 “ 精装公寓 “ 。你不用关心公寓在哪,只管拎包入住(存数据)。Docker 负责创建、管理和清理。
    • 由 Docker 管理的存储空间,存储在 Docker 主机的特定目录下(Linux 通常在 /var/lib/docker/volumes/
    • ❌ 依赖主机路径

看冒号 (:) 左边的部分:

  • 如果它包含 /、./、../ (在 Linux/macOS) 或 \ (在 Windows),那么它一定是 Bind Mount。 因为你在指定一个明确的、存在于宿主机文件系统上的路径。
  • 如果它只是一个简单的字符串,不包含任何路径分隔符,那么它一定是 Volume。 因为你只是在给 Docker 一个名字,让 Docker 自己去管理这个存储空间。
1
2
3
4
5
6
7
8
9
10
11
12
13
version: '3'
services:
app:
image: myapp
volumes:
# Bind Mount - 开发时的代码
- ./src:/app/src
# Volume - 持久化数据
- app-data:/app/data
# Bind Mount - 配置文件(只读)
- ./config/app.conf:/app/config/app.conf:ro
volumes:
app-data:

2. 使用技巧

2.1 指定 IP 执行命令

DOCKER_HOST 是 Docker 的一个环境变量,用于指定 Docker 客户端要连接的 Docker 守护进程(daemon)的地址。

1
DOCKER_HOST=ssh://root@10.133.0.21 docker ps

2.2 查看 Docker 实时监控

1
docker stats  

2.3 告别宿主机

1
2
3
# 可以轻松切换不同版本的客户端
docker run -it --rm redis:6 redis-cli -h 10.0.114.5
docker run -it --rm redis:7 redis-cli -h 10.0.114.5
  1. 无需本地安装 Redis 客户端,保持本地环境清洁。
  2. 可以轻松切换不同版本的客户端。
  3. -rm 确保容器用完即删,不占用系统资源。
  4. Docker 容器默认可以访问宿主机能访问的网络。

2.4 多利用 Docker-compose

  1. 提前拷贝 package 缓存
  2. 利用数据卷防止 node_modules 被覆盖
  3. 利用 docker-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
FROM node:lts-alpine

RUN apk add --no-cache git openssh-client

RUN npm config set registry https://registry.npmmirror.com

WORKDIR /app

# 【关键】将 node_modules 的可执行文件目录添加到环境变量 PATH 中
ENV PATH /app/node_modules/.bin:$PATH
# 这是利用 Docker 缓存的关键!只要这两个文件不变,下面的 npm install 就不会重新执行。
COPY package*.json ./

# 2. 安装 hexo-cli 和项目所有依赖
# --production=false 确保 devDependencies 也被安装
RUN npm install --production=false hexo-cli && npm install --production=false

# 3. 拷贝你博客的所有其他文件(比如 markdown 文章、主题配置等)
# 这一步放在最后,因为这些文件是你最常修改的
COPY . .

# 暴露 hexo server 的默认端口
EXPOSE 4000

# 设置容器启动时默认执行的命令
CMD ["hexo", "server"]
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
services:
# 测试
hexo-liuvv:
build:
context: .
dockerfile: Dockerfile
image: my-hexo-liuvv
container_name: hexo_blog_liuvv
ports:
- "4000:4000"
volumes:
- .:/app
# 【关键技巧】这是一个匿名卷,用于隔离 node_modules 目录。作用:防止本地映射覆盖掉镜像里已经安装好的 node_modules,
- /app/node_modules
command: sh -c "hexo server"

# 部署
hexo-deployer:
image: my-hexo-liuvv
volumes:
# 1. 挂载源码,确保部署的是最新内容
- .:/app
# 2. 挂载你的 SSH 密钥,以便能推送到 GitHub
- ~/.ssh:/root/.ssh:ro
# 3. (新增) 将你主机的 Git 配置挂载到容器中
- ~/.gitconfig:/root/.gitconfig:ro
# 4. (推荐) 保留 node_modules 的匿名卷
- /app/node_modules
command: >
sh -c "hexo clean && hexo g -d"

2.5 Docker Images 清理

1
2
3
4
docker images 有很多 none, 会占用磁盘吗?怎么清理

<none> <none> 6038648f7701 9 minutes ago 428MB
<none> <none> aeed82987cc4 9 minutes ago 428MB
1
docker image prune -f

2.6 Docker 容器外更新代码

1
2
3
4
5
6
7
8
9
docker rm -f dev-ifonly-server
docker run -d --restart always --name dev-ifonly-server -p 8899:8888 -p 8890:8890 \
-v /root/scripts/jenkins/jenkins_home/workspace/"$JOB_NAME":/app:ro \
-v /home/ubuntu/.gitconfig:/root/.gitconfig:ro \
-v /home/ubuntu/.git-credentials:/root/.git-credentials:ro \
-v go-pkg-mod:/go/pkg/mod \
-v go-build-cache:/root/.cache/go-build \
--log-driver=fluentd --log-opt fluentd-address=log-collector-internal.ifonlyapp.com:9997 \
-w /app -e GOPRIVATE='git.hoxigames.xyz' -e TZ='Asia/Shanghai' golang:1.21 go run . -f /app/etc/dev/config.yaml

最好借助 docker-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
36
37
38
39
40
41
42
43
44
# 使用一个较新的、稳定的版本
version: '3.8'

services:
# 这是你的服务名,可以自定义
ifonly-server:
# 容器名,对应 --name dev-ifonly-server
container_name: dev-ifonly-server
# 镜像,对应 golang:1.21
image: golang:1.21
# 重启策略,对应 --restart always
restart: always
# 端口映射,对应 -p
ports:
- "8899:8888"
- "8890:8890"
# 卷挂载,对应 -v
volumes:
# 注意:${JOB_NAME} 会读取你运行 docker-compose 命令时环境中的变量
- /root/scripts/jenkins/jenkins_home/workspace/${JOB_NAME}:/app:ro
- /home/ubuntu/.gitconfig:/root/.gitconfig:ro
- /home/ubuntu/.git-credentials:/root/.git-credentials:ro
# 这是具名卷 (Named Volumes),更方便管理
- go-pkg-mod:/go/pkg/mod
- go-build-cache:/root/.cache/go-build
# 工作目录,对应 -w /app
working_dir: /app
# 环境变量,对应 -e
environment:
- GOPRIVATE=git.hoxigames.xyz
- TZ=Asia/Shanghai
# 日志驱动,对应 --log-driver 和 --log-opt
logging:
driver: fluentd
options:
fluentd-address: "log-collector-internal.ifonlyapp.com:9997"
# 容器启动后执行的命令,对应 go run . ...
command: ["go", "run", ".", "-f", "/app/etc/dev/config.yaml"]

# 在这里声明具名卷,Docker 会自动为你创建和管理它们
volumes:
go-pkg-mod:
go-build-cache:

1
2
docker-compose up -d
docker-compose down

2.7 使用非宿主机的 Go

1
2
3
4
5
6
7
8
9
10
11
12

go build cmd/main.go -o lora-train-platform-server

# 改成下面这样:
docker pull golang:1.24.6
docker run --rm \
-v "$(pwd)":/app \
-w /app \
-e GOPROXY=https://goproxy.cn,direct \
golang:1.24.6 \
go build -o lora-train-platform-server cmd/main.go

  • -v "$(pwd)":/app
    • 这是整个命令的魔法核心!-v 是 --volume 的缩写,用于将宿主机的文件或目录挂载到容器内部。
    • "$(pwd)":在 Linux 和 macOS 中,这会自动替换为你的当前工作目录的绝对路径。如果你在 Windows 的 PowerShell 中,"$(pwd)" 也可以工作;如果是在 CMD 中,则需要使用 "%cd%"
    • :/app:这是将宿主机目录挂载到容器内的 /app 目录。
    • 效果:这行命令相当于在容器里创建了一个通往你宿主机项目文件夹的 “ 传送门 “。容器内的 Go 编译器就能读到你的 cmd/main.go 以及其他所有源代码文件了。
  • -w /app
    • -w 是 --workdir 的缩写,它指定了容器内命令执行的工作目录。
    • 因为我们已经将项目挂载到了 /app,所以我们将工作目录也设置在这里。这样,go build 命令后面的相对路径 cmd/main.go 才能被正确找到。