目录

Docker 入门指南:从核心概念到容器化应用实践

Docker 入门指南:从核心概念到容器化应用实践

一、Docker 是什么?

Docker 是一种用于运行和管理容器化应用的技术。

它解决的一个核心问题是:

如何让应用及其运行环境保持一致,从而避免“在我电脑上能运行,换一台机器就不能运行”的问题。

一个应用通常依赖很多外部环境,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Node.js 版本
Python 版本
Go 版本
Nginx
MySQL
PostgreSQL
系统库
第三方依赖
环境变量
配置文件

如果开发环境和生产环境的版本不同,就可能出现:

1
2
3
4
5
6
7
8
9
开发环境:
Node.js 20
MySQL 8
Python 3.11

生产环境:
Node.js 18
MySQL 5.7
Python 3.9

即使代码完全相同,最终运行结果也可能不同。

Docker 的思路是:

1
2
3
4
5
6
7
8
9
应用程序
+
依赖环境
+
运行配置
        ↓
    Docker Image
        ↓
    Container

把应用运行所需要的环境标准化,然后使用容器运行。


二、Docker 的四个核心对象

Docker 中最重要的四类对象是:

1
2
3
4
Image
Container
Volume
Network

可以先建立这样一个整体认识:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
                    Docker
                      │
        ┌─────────────┼─────────────┐
        │             │             │
      Image       Container       Volume
        │             │             │
      模板          运行实例       持久化数据
                      │
                      │
                    Network
                      │
                  容器之间通信

其中最需要理解的是:

Image 是模板,Container 是运行实例。


三、Image:Docker 镜像

Docker Image 可以理解为:

创建容器所使用的模板。

例如:

1
2
3
4
nginx:latest
redis:7
mysql:8
golang:1.18

这些都是 Docker Image。

一个镜像中通常包含:

1
2
3
4
5
6
应用程序
运行时环境
依赖
文件系统
配置
启动信息

例如:

1
golang:1.18

可以理解为:

1
2
3
4
5
6
7
Linux 基础环境
+
Go 1.18
+
Go 编译工具
+
相关运行环境

然后基于这个 Image 创建 Container。


四、Container:Docker 容器

Container 是镜像运行后的实例。

可以用一个非常简单的关系理解:

1
2
3
Image
  ↓ docker run
Container

例如:

1
docker run nginx

Docker 会:

1
2
3
4
5
nginx Image
    ↓
创建
    ↓
nginx Container

因此:

1
2
Image      = 模板
Container  = 实例

同一个 Image 可以创建多个 Container:

1
2
3
4
5
             nginx:latest
                  │
       ┌──────────┼──────────┐
       ↓          ↓          ↓
   nginx-1    nginx-2    nginx-3

这也是容器化部署能够快速扩展的基础。


五、Image 和 Container 的关系

可以类比成:

1
2
3
4
5
面向对象:

Class
  ↓
Object

对应:

1
2
3
4
5
Docker:

Image
  ↓
Container

Image 本身不是正在运行的应用。

真正运行的是 Container。


六、Docker Registry:镜像仓库

镜像可以保存到 Registry 中。

最常见的是:

1
Docker Hub

除此之外,还有:

1
2
3
4
5
Harbor
GitLab Container Registry
阿里云容器镜像服务
AWS ECR
GitHub Container Registry

可以把 Registry 理解成:

Docker 镜像的远程仓库。

例如:

1
docker pull nginx

实际上就是:

1
2
3
4
5
本地 Docker
    ↓
Registry
    ↓
下载 nginx Image

构建好的镜像也可以:

1
docker push

上传到镜像仓库。

因此一个典型的 CI/CD 流程可以是:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Git
 ↓
CI
 ↓
docker build
 ↓
Docker Image
 ↓
docker push
 ↓
Container Registry
 ↓
服务器 / Kubernetes
 ↓
docker pull
 ↓
运行 Container

七、Dockerfile:如何制作镜像

Dockerfile 是:

描述如何构建 Docker Image 的文件。

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
FROM golang:1.18

WORKDIR /app

COPY . .

RUN go build -o server .

EXPOSE 8080

CMD ["./server"]

Docker 根据 Dockerfile:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
FROM
 ↓
选择基础镜像

COPY
 ↓
复制代码

RUN
 ↓
执行构建命令

EXPOSE
 ↓
声明应用端口

CMD
 ↓
定义默认启动命令

最终:

1
2
3
4
5
Dockerfile
    ↓ docker build
Docker Image
    ↓ docker run
Container

八、Dockerfile 常用指令

最常用的指令包括:

指令作用
FROM指定基础镜像
RUN构建镜像时执行命令
COPY复制文件
ADD复制文件,具有额外功能
WORKDIR设置工作目录
ENV设置环境变量
ARG定义构建参数
EXPOSE声明应用端口
CMD定义默认启动命令
ENTRYPOINT定义容器入口程序
USER指定运行用户
VOLUME声明数据卷

其中最值得优先掌握:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
FROM
COPY
RUN
WORKDIR
CMD
ENTRYPOINT
ENV
ARG
EXPOSE
USER

九、.dockerignore

当 Dockerfile 中存在:

1
COPY . .

Docker 会把构建上下文中的文件用于构建。

因此通常应该配置:

1
.dockerignore

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
.git
.gitignore
.idea
.vscode

*.log

tmp/
logs/

dist/
build/

coverage/

这样可以避免:

1
2
3
4
5
6
Git 历史
IDE 配置
日志
临时文件
构建产物
测试数据

等无用内容进入构建上下文。

如果其中包含:

1
.env

还可以避免将包含敏感信息的文件意外复制到镜像中。

但是需要注意:

.dockerignore 主要解决的是构建上下文和复制问题,不能替代真正的密钥管理方案。


十、为什么要关注镜像大小?

镜像越大,通常意味着:

1
2
3
4
5
构建时间更长
上传时间更长
下载时间更长
CI/CD 时间更长
存储成本更高

例如:

1
1 GB Image

如果需要频繁:

1
2
3
4
build
push
pull
deploy

这些额外的数据传输都会累积成本。

因此 Docker 镜像通常希望:

在满足应用运行需求的前提下尽可能精简。


十一、基础镜像应该怎么选择?

常见基础镜像包括:

1
2
3
4
5
Ubuntu
Debian
Alpine
Distroless
Scratch

它们并不存在绝对的“最好”。

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Ubuntu / Debian
    ↓
兼容性较好
工具比较完整

Alpine
    ↓
体积较小
用户空间比较精简

Distroless
    ↓
运行环境进一步精简

scratch
    ↓
几乎没有用户空间

因此选择基础镜像时应该考虑:

1
2
3
4
5
6
7
程序运行依赖
C/C++ 动态库
证书
时区
调试需求
安全要求
镜像大小

而不是单纯追求:

“镜像越小越好。”


十二、多阶段构建

多阶段构建是 Dockerfile 中非常重要的实践。

尤其对于:

1
2
3
4
Go
Java
Rust
C/C++

等需要编译的项目。

例如 Go:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
FROM golang:1.18 AS builder

WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

COPY . .

RUN CGO_ENABLED=0 \
    GOOS=linux \
    GOARCH=amd64 \
    go build -o server .

FROM alpine:3.18

WORKDIR /app

COPY --from=builder /app/server ./server

EXPOSE 8080

ENTRYPOINT ["./server"]

这里实际上有两个阶段:

1
2
3
4
5
6
7
8
9
第一阶段
golang:1.18
    ↓
负责编译

第二阶段
alpine
    ↓
负责运行

最终运行镜像中不需要保留:

1
2
3
4
Go Compiler
Go SDK
源码
编译工具

只需要:

1
2
3
server
+
运行时需要的文件

因此可以显著减少最终镜像内容。


十三、Docker Container 的基本操作

启动一个容器:

1
docker run nginx

后台运行:

1
docker run -d nginx

指定名称:

1
2
3
docker run -d \
  --name nginx \
  nginx

查看运行中的容器:

1
docker ps

查看所有容器:

1
docker ps -a

查看容器日志:

1
docker logs nginx

持续查看日志:

1
docker logs -f nginx

进入正在运行的容器:

1
docker exec -it nginx sh

查看容器详细信息:

1
docker inspect nginx

停止:

1
docker stop nginx

删除:

1
docker rm nginx

强制删除:

1
docker rm -f nginx

十四、容器为什么需要端口映射?

假设容器中的 Web 服务监听:

1
8080

Dockerfile:

1
EXPOSE 8080

运行:

1
docker run -p 8080:8080 app

表示:

1
2
3
宿主机 8080
      ↓
容器 8080

需要特别注意:

1
EXPOSE 8080

只是声明端口。

它并不会自动创建:

1
宿主机 → 容器

的端口映射。

真正完成映射的是:

1
-p 8080:8080

十五、Docker Volume:数据持久化

容器有一个非常重要的特点:

容器本身通常应该被认为是可以随时销毁和重新创建的。

因此,如果把重要数据直接存放在容器可写层:

1
2
3
4
5
Container
   ↓
删除
   ↓
数据可能丢失

对于:

1
2
3
数据库
上传文件
持久化数据

显然是不合适的。

Docker Volume 就是解决这个问题的。

例如:

1
docker volume create db-data

然后:

1
2
3
docker run \
  -v db-data:/var/lib/mysql \
  mysql

此时:

1
2
3
4
5
6
7
Container
    │
    ↓
Volume
    │
    ↓
持久化数据

即使容器被删除:

1
docker rm mysql

Volume 仍然可以保留。

之后重新创建容器:

1
2
3
4
5
新 Container
     ↓
挂载原来的 Volume
     ↓
继续使用原来的数据

十六、Docker Network:容器之间如何通信?

Docker Network 用于:

连接和隔离 Docker 容器。

例如:

1
2
3
4
5
6
7
8
9
web
 │
 │ HTTP
 ↓
api
 │
 │ TCP
 ↓
mysql

可以创建一个网络:

1
docker network create app-net

然后:

1
2
3
4
docker run -d \
  --network app-net \
  --name api \
  api:1.0

数据库:

1
2
3
4
docker run -d \
  --network app-net \
  --name mysql \
  mysql:8

同一个 Docker Network 中的容器可以通过容器名称进行通信。

例如:

1
api → mysql:3306

而不需要依赖容器不断变化的 IP 地址。


十七、Docker Compose

当应用只有一个容器时:

1
docker run ...

通常还能接受。

但如果一个项目需要:

1
2
3
4
5
Web
API
MySQL
Redis
Nginx

每次手动执行大量 docker run 命令就比较麻烦。

Docker Compose 的作用就是:

用一个 YAML 文件描述多个容器及其关系。

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
services:
  api:
    build: ./api
    ports:
      - "8080:8080"

  db:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: password
    volumes:
      - db-data:/var/lib/mysql

volumes:
  db-data:

然后:

1
docker compose up

就可以启动整个应用。

后台运行:

1
docker compose up -d

停止并删除相关资源:

1
docker compose down

查看服务:

1
docker compose ps

查看日志:

1
docker compose logs

需要注意:

文章原始版本发表于 2020 年,当时大量使用 docker-compose 命令;现代 Docker Compose 已经统一使用 docker compose 子命令形式。

因此现在更推荐:

1
docker compose up

而不是:

1
docker-compose up

十八、环境变量

容器中的配置通常不应该全部写死在镜像里。

例如:

1
2
3
4
5
6
数据库地址
数据库密码
Redis 地址
运行环境
服务端口
第三方服务地址

可以通过环境变量传递。

例如:

1
2
3
4
docker run \
  -e APP_ENV=prod \
  -e DB_HOST=mysql \
  my-app:1.0

Docker Compose 中:

1
2
3
4
5
6
services:
  api:
    image: my-api:1.0
    environment:
      APP_ENV: prod
      DB_HOST: mysql

也可以使用:

1
2
3
4
services:
  api:
    env_file:
      - .env

但需要注意:

.env 文件中如果存在密码、Token、API Key 等敏感信息,不应该直接提交到 Git 仓库。

生产环境还应该考虑专门的 Secret 管理方案。


十九、Docker 中的数据和配置应该如何理解?

可以形成一个非常重要的原则:

1
2
3
Image
 ↓
应用程序和运行环境
1
2
3
Container
 ↓
应用运行实例
1
2
3
Volume
 ↓
持久化数据
1
2
3
Environment / Config
 ↓
运行配置
1
2
3
Network
 ↓
容器之间的通信

因此:

不要把 Container 当成传统服务器。

传统服务器可能:

1
2
3
4
运行很久
修改配置
手动安装软件
保存数据

而容器更倾向于:

1
2
3
4
5
6
7
8
9
构建 Image
     ↓
运行 Container
     ↓
发现版本问题
     ↓
重新构建 Image
     ↓
重新创建 Container

也就是:

修改镜像,而不是长期修改正在运行的容器。


二十、Docker 的典型应用流程

一个比较完整的 Docker 开发流程:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
             编写代码
                ↓
            Dockerfile
                ↓
          docker build
                ↓
             Image
                ↓
          本地测试
                ↓
          docker push
                ↓
        Container Registry
                ↓
         服务器 / K8s
                ↓
           docker pull
                ↓
           Container

如果加入 CI/CD:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Git Push
   ↓
CI Pipeline
   ↓
Unit Test
   ↓
docker build
   ↓
Image Scan
   ↓
docker push
   ↓
Registry
   ↓
Deploy
   ↓
Kubernetes

Docker 在现代云原生体系中,更多是:

应用交付和运行时封装的一环。

而不是整个部署体系本身。


二十一、Docker 和虚拟机的区别

Docker 容器和虚拟机都可以提供隔离环境,但实现方式不同。

虚拟机:

1
2
3
4
5
6
7
8
9
Physical Machine
       ↓
Hypervisor
       ↓
Virtual Machine
       ↓
Guest OS
       ↓
Application

容器:

1
2
3
4
5
6
7
8
9
Physical Machine
       ↓
Host OS / Linux Kernel
       ↓
Container Runtime
       ↓
Container
       ↓
Application

最核心的区别是:

容器通常共享宿主机的 Linux Kernel,而虚拟机拥有自己的 Guest OS。

因此容器通常:

1
2
3
4
启动更快
资源开销更低
镜像更小
部署更加轻量

而虚拟机提供的 OS 级隔离更完整。

所以不能简单理解成:

1
Docker = 更先进的虚拟机

它们解决的问题存在重叠,但底层机制不同。


二十二、Docker Engine

Docker Engine 可以理解为 Docker 的核心运行环境。

其中:

1
2
3
4
5
6
7
Docker CLI
    ↓
Docker API
    ↓
Docker Daemon
    ↓
Container / Image / Network / Volume

开发者最常使用:

1
docker

命令行工具。

例如:

1
2
3
4
5
6
docker ps
docker images
docker build
docker run
docker exec
docker logs

CLI 最终通过 Docker Engine 提供的 API 与 Docker daemon 交互。


二十三、Docker 中最重要的几个认知

如果把整个 Docker 入门内容浓缩一下,可以得到下面这套模型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
                    Docker
                       │
        ┌──────────────┼──────────────┐
        ↓              ↓              ↓
      Image         Container       Registry
        │              │
      模板           运行实例
                       │
              ┌────────┴────────┐
              ↓                 ↓
           Volume            Network
            数据              通信

应用开发:

1
2
3
4
5
Dockerfile
    ↓
docker build
    ↓
Image

应用运行:

1
2
3
4
5
Image
    ↓
docker run
    ↓
Container

数据持久化:

1
2
3
Container
    ↓
Volume

容器通信:

1
2
3
Container
    ↓
Network

镜像共享:

1
2
3
Image
    ↓
Registry

多容器应用:

1
Docker Compose

大规模容器编排:

1
Kubernetes

二十四、几个非常值得记住的 Docker 原则

原则一:Image 和 Container 不要混淆

1
2
Image = 模板
Container = 实例

原则二:容器应该尽量无状态

需要保存的数据:

1
2
3
4
Volume
数据库
对象存储
外部存储

不要依赖容器自身保存重要数据。

原则三:尽量使用不可变镜像

不要长期:

1
docker exec container ...

然后在里面手工安装一堆软件。

更推荐:

1
2
3
4
5
6
7
修改 Dockerfile
    ↓
重新 build
    ↓
新 Image
    ↓
重新部署

原则四:生产镜像尽量精简

可以使用:

1
2
3
4
5
多阶段构建
.dockerignore
精简基础镜像
只复制运行所需文件
非 root 用户

原则五:配置与镜像分离

例如:

1
2
3
4
5
6
7
Image
    ↓
代码 + 运行环境

Environment / Config
    ↓
环境相关配置

这样同一个镜像:

1
my-app:1.0

可以部署到:

1
2
3
开发环境
测试环境
生产环境

而不需要为每个环境重新构建一份镜像。


二十五、Docker 学习路线

如果按照实际开发使用来学习 Docker,可以按照下面的顺序:

 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
① Docker 基本概念
   │
   ├── Image
   ├── Container
   ├── Registry
   ├── Volume
   └── Network
   ↓
② Dockerfile
   │
   ├── FROM
   ├── RUN
   ├── COPY
   ├── WORKDIR
   ├── CMD
   └── ENTRYPOINT
   ↓
③ Container 操作
   │
   ├── run
   ├── ps
   ├── logs
   ├── exec
   ├── stop
   └── rm
   ↓
④ 镜像优化
   │
   ├── .dockerignore
   ├── Build Cache
   ├── Multi-stage Build
   └── 非 root
   ↓
⑤ Volume / Network
   ↓
⑥ Docker Compose
   ↓
⑦ Registry
   ↓
⑧ CI/CD
   ↓
⑨ Kubernetes

其中对于 Go 开发者,建议重点掌握:

1
2
3
4
5
6
7
8
Dockerfile
多阶段构建
镜像优化
容器生命周期
Volume
Network
Docker Compose
Registry

然后再进一步进入:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Docker
   ↓
Kubernetes
   ↓
Helm
   ↓
CI/CD
   ↓
容器安全
   ↓
可观测性

这样 Docker 就不再是孤立的一项技术,而是整个云原生工程体系中的一个组成部分。


二十六、总结

Docker 最核心的思想可以浓缩成一句话:

把应用程序及其运行所需要的环境封装成标准化的镜像,再通过容器运行这个镜像。

理解下面这条链路,就基本建立了 Docker 的整体认知:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Source Code
     ↓
Dockerfile
     ↓
docker build
     ↓
Docker Image
     ↓
Docker Registry
     ↓
docker pull
     ↓
Docker Container
     ↓
Volume / Network
     ↓
Application

而对于实际开发,最重要的不是记住所有 Docker 命令,而是理解:

1
2
3
4
5
6
7
8
Image       → 应用运行模板
Container   → Image 的运行实例
Dockerfile  → Image 的构建说明
Registry    → Image 的存储和分发
Volume      → 数据持久化
Network     → 容器通信
Compose     → 多容器应用管理
Kubernetes  → 大规模容器编排

当这些概念建立起来之后,再学习 Dockerfile、多阶段构建、Compose、Kubernetes,就会从“记命令”变成“理解系统”。