Docker学习笔记——基础篇

cuixiaogang

概述

Docker是一个开源的应用容器引擎,基于Go语言,并遵从Apache2.0协议开源。它可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化

应用场景

  • 微服务架构:每个服务独立容器化,便于管理和扩展
  • CI/CD流水线:与Jenkins/GitLabCI集成,实现自动化构建和测试
  • 开发环境标准化:新成员一键启动全套依赖服务(如数据库、消息队列)
  • 云原生基础:Kubernetes等编排工具基于Docker管理容器集群

核心优势

  • 跨平台一致性:解决“在我机器上能跑”的问题,确保开发、测试、生产环境一致
  • 资源高效:容器直接共享主机内核,无需虚拟化整个操作系统,节省内存和CPU
  • 快速部署:秒级启动容器,支持自动化扩缩容
  • 隔离性:每个容器拥有独立的文件系统、网络和进程空间

核心概念

基本概念

  • 容器(Container):轻量化的运行实例,包含应用代码、运行时环境和依赖库。基于镜像创建,与其他容器隔离,共享主机操作系统内核(比虚拟机更高效)
  • 镜像(Image):只读模板,定义了容器的运行环境(如操作系统、软件配置等)。通过分层存储(Layer)优化空间和构建速度
  • Dockerfile:文本文件,描述如何自动构建镜像(例如指定基础镜像、安装软件、复制文件等)
  • 仓库(Registry):存储和分发镜像的平台,如Docker Hub(官方公共仓库)或私有仓库(如Harbor)

容器与虚拟机的区别

容器和虚拟机都能做环境隔离,但实现层次完全不同。虚拟机靠Hypervisor虚拟化出一整套硬件,每台虚拟机里跑一个完整的操作系统(含独立内核);容器则直接共享宿主机内核,只把应用和依赖打包进去,靠Linux的namespace做视图隔离、cgroup做资源限制。

架构上的差异:

容器与虚拟机的架构差异
容器与虚拟机的架构差异

虚拟机每层都背着一个完整的Guest OS,容器把Guest OS和Hypervisor这两层省掉了,直接复用宿主机内核——这是它更轻、更快的根本原因。

详细对比:

对比项 虚拟机 容器
隔离级别 硬件级,靠Hypervisor 进程级,靠namespace/cgroup
是否含独立内核 是,每个VM一套完整OS 否,共享宿主机内核
启动速度 分钟级 秒级甚至毫秒级
资源占用 GB级(含整个OS) MB级(只有应用+依赖)
性能损耗 有虚拟化开销 接近原生
隔离强度 强(攻击面小) 相对弱(共享内核)
单机可运行数量 几台到十几台 几十到上百个

一句话记住:虚拟机虚拟的是硬件,容器隔离的是进程。所以容器不是”更轻量的虚拟机”,两者根本不在一个层次上。需要强隔离时(如多租户、跑不可信代码)虚拟机仍不可替代;追求快速部署、高密度、环境一致时容器更合适。两者也常配合——在虚拟机里跑容器是生产环境里很常见的做法。

架构与工作流程

架构

  • Docker架构是基于客户端-服务器模式的,其中包括多个关键组件,确保容器化应用的高效构建、管理和运行
  • Docker的架构设计使得开发者能够轻松地将应用程序与其所有依赖封装在一个可移植的容器中,并在不同的环境中一致地运行
  • Docker使用客户端-服务器 (C/S)架构模式,使用远程API来管理和创建Docker容器
  • Docker容器通过Docker镜像来创建

容器与镜像的关系类似于面向对象编程中的对象与类

Docker架构示意图
Docker架构示意图

工作流程

  • 构建镜像:使用Dockerfile创建镜像
  • 推送镜像到注册表:将镜像上传到Docker Hub或私有注册表中
  • 拉取镜像:通过docker pull从注册表中拉取镜像
  • 运行容器:使用镜像创建并启动容器
  • 管理容器:使用Docker客户端命令管理正在运行的容器(例如查看日志、停止容器、查看资源使用情况等)
  • 网络与存储:容器之间通过Docker网络连接,数据通过 Docker 卷或绑定挂载进行持久化

安装与命令

安装及部署

安装文档:https://docs.docker.com/engine/install

核心命令

Docker的命令大体分两类:操作镜像(image)和操作容器(container)。记住下面这批,入门阶段的绝大多数场景就够用了。

镜像相关:

命令 作用
docker pull [image]{:tag} 从仓库拉取镜像(不写tag默认latest)
docker images 列出本地所有镜像
docker rmi [image] 删除本地镜像
docker search [image] 在Docker Hub搜索镜像
docker tag [旧名] [新名] 给镜像打标签(常用于推送前重命名)

容器相关:

命令 作用
docker run 镜像 基于镜像创建并启动容器(本地没有会自动pull)
docker ps 查看正在运行的容器
docker ps -a 查看所有容器(含已停止的)
docker stop [container] 停止容器
docker start [container] 启动已停止的容器
docker restart [container] 重启容器
docker rm [container] 删除容器(需先停止,或加 -f 强制删)
docker logs [container] 查看容器日志
docker exec -it [container] bash 进入正在运行的容器内部

容器和镜像在命令里都可以用名字或ID引用,ID只需写前几位能唯一区分即可

有个容易混的点:docker stop只是停掉容器,容器本身还在(docker ps -a仍能看到),要彻底清掉得再docker rm。删容器用rm,删镜像用rmi,别搞混。

docker run 命令详解

docker run是入门阶段用得最多的命令,本质是「创建容器」加「启动容器」两步合一。前面「核心命令」给了一张速查表,这里把常用参数讲细一点,重点是--name--rm-v-p这几个高频项。

命令的基本结构是固定的:

1
docker run [参数] 镜像名[:标签] [传给容器的命令]

注意参数必须写在镜像名之前。镜像名之后的内容会被当成「覆盖镜像默认启动命令」传进容器,位置写错行为就变了,这是个容易踩的坑。

常用参数:

参数 全称 作用与说明
-d --detach 后台运行,立即返回容器ID,不占住终端。不加则前台运行,日志直接打到当前终端
-it --interactive --tty 组合使用:-i保持标准输入打开,-t分配伪终端。跑交互式shell必加,如docker run -it ubuntu bash
--name 给容器起名,不指定则随机生成(如nervous_tesla)。后续stop/rm/exec可直接用名字引用,强烈建议都起名
--rm 容器一退出就自动删除,适合一次性测试,免得堆一堆已停止的容器。和-d长期服务一般不一起用
-p --publish 端口映射,宿主机端口:容器端口,如-p 8080:80。可写多次映射多个端口,也可绑定指定网卡-p 127.0.0.1:8080:80
-P --publish-all 把镜像里EXPOSE声明的端口随机映射到宿主机高位端口
-v --volume 挂载,宿主机路径:容器路径[:ro],如-v /home/data:/service/data;也可挂命名卷myvol:/data。结尾加:ro表示容器内只读
-e --env 设置环境变量,如-e MYSQL_ROOT_PASSWORD=123456。多个变量写多次,或用--env-file从文件读入
-w --workdir 指定容器内工作目录,对应inspect里的WorkingDir
--network 指定加入的网络,默认bridge。可选host(共享宿主网络栈)、none(无网络)或自建网络
--restart 重启策略:no(默认)、on-failure[:次数]alwaysunless-stopped。常驻服务常用后两者
--privileged 赋予容器近乎宿主机的权限,有安全风险,非必要不用
--entrypoint 覆盖镜像默认的ENTRYPOINT

-v挂载有两种常见形态:绑定宿主机目录(bind,路径以/开头)和命名卷(volume,写卷名)。开发期想直接改宿主机文件用bind最方便;生产里要Docker托管数据多用命名卷。

下面以我的开发环境启动的命令作为示例:

1
2
3
4
5
6
7
8
docker run -d \
--name cxg_dev_go124 \
--restart unless-stopped \
-p 15401:15401 -p 15402:15402 \
-v /home/cuixiaogang/www:/service/www \
-v /home/cuixiaogang/data:/service/data \
-w /service/data \
cxg_dev_go_1_24_4:1.1

dev_go开发容器
dev_go开发容器

这条命令有如下限制:

  • 后台运行
  • 宕机或重启后自动拉起
  • 映射两个端口
  • 把宿主机两个目录挂进容器
  • 把工作目录设为/service/data

docker inspect 详解

docker inspect用来查看Docker对象(容器、镜像、网络、卷)的底层完整元数据,输出是一个JSON数组。容器拿不到预期IP、端口没映射上、挂载路径不对——排查这类问题时,inspect是第一手信息来源。

常用形式:

命令 作用
docker inspect [container名/ID] 输出该对象的完整JSON(数组形式)
docker inspect --type container [container名/ID] 限定类型,同名镜像和容器并存时用
docker inspect -f '{{.State.Status}}' [container名/ID] 用Go模板只取某个字段,不必翻一大坨输出
docker inspect -f '{{.NetworkSettings.IPAddress}}' [container名/ID] 高频用法:取容器IP
docker inspect -f '{{json .NetworkSettings.Ports}}' [container名/ID] 以JSON形式输出某个子结构

-f/--format走的是Go的text/template语法,.代表整个JSON对象,沿层级用点号往下取。这是inspect最实用的能力。

下面是一份刚刚启动的开发环境的容器的inspect详细信息(端口只保留2个,并去掉了overlay2层路径、ResolvConfPath等冗长字段,方便聚焦):

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
83
84
85
[
{
"Id": "8ce174c5744090df5cd43760c485da7cd9532e25721999b780c3b7aff700c869",
"Created": "2025-07-04T02:51:57.840322565Z",
"Path": "/service/run.sh",
"Args": [],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 21075,
"ExitCode": 0,
"Error": "",
"StartedAt": "2025-12-29T02:39:20.528658607Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
"Image": "sha256:20ef2b5cc9fd56874269dba0e15ab1bdf035cbb471b6387a7a0287ebc96a0f4e",
"Name": "/cxg_dev_go124",
"RestartCount": 0,
"Driver": "overlay2",
"Platform": "linux",
"HostConfig": {
"Binds": [
"/home/cuixiaogang/www:/service/www",
"/home/cuixiaogang/data:/service/data"
],
"NetworkMode": "default",
"PortBindings": {
"15401/tcp": [ { "HostIp": "", "HostPort": "15401" } ],
"15402/tcp": [ { "HostIp": "", "HostPort": "15402" } ]
},
"RestartPolicy": { "Name": "no", "MaximumRetryCount": 0 },
"Privileged": false
},
"Mounts": [
{
"Type": "bind",
"Source": "/home/cuixiaogang/data",
"Destination": "/service/data",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
"Config": {
"Hostname": "8ce174c57440",
"User": "",
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/service/app/go-1.24.4/bin"
],
"Cmd": [ "/service/run.sh" ],
"Entrypoint": null,
"Image": "cxg_dev_go_1_24_4:1.1",
"WorkingDir": "/service/data",
"ExposedPorts": {
"15401/tcp": {},
"15402/tcp": {}
},
"Labels": {
"maintainer": "cuixiaogang@360.cn"
}
},
"NetworkSettings": {
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.3",
"IPPrefixLen": 16,
"MacAddress": "02:42:ac:11:00:03",
"Ports": {
"15401/tcp": [ { "HostIp": "0.0.0.0", "HostPort": "15401" } ],
"15402/tcp": [ { "HostIp": "0.0.0.0", "HostPort": "15402" } ]
},
"Networks": {
"bridge": {
"IPAddress": "172.17.0.3",
"Gateway": "172.17.0.1",
"MacAddress": "02:42:ac:11:00:03",
"NetworkID": "1fd07e115f24735776de6ee125ecc295f2d3d36ccc9e9ed38f67e701dbac2e58"
}
}
}
}
]

字段说明:

  • 顶层身份与基本信息:
字段 含义
Id 容器完整ID(64位十六进制),命令里只用前几位能唯一区分即可
Created 容器创建时间(UTC)
Path/Args 容器启动时执行的命令及其参数,这里是/service/run.sh
Image 所基于镜像的ID(sha256哈希),给机器用。注意和Config.Image区分
Name 容器名,前面那个/是历史遗留写法
RestartCount 已重启次数
Driver 存储驱动,这里是overlay2
Platform 平台,linux
  • State——运行状态,排查容器异常退出时第一个看的地方:
字段 含义
Status 当前状态:running/exited/paused
Running/Paused/Restarting/Dead 各状态的布尔标志
OOMKilled 是否因内存超限被系统OOM杀掉,容器莫名死掉时重点看它
Pid 容器主进程在宿主机上的真实PID
ExitCode 退出码,0正常、非0异常
StartedAt/FinishedAt 最近一次启动/停止时间,运行中FinishedAt为零值
  • HostConfig——宿主机层面的运行配置,基本和docker run的参数一一对应:
字段 对应/含义
Binds 目录挂载宿主机:容器,对应-v
NetworkMode 网络模式,对应--network
PortBindings 端口映射,对应-p。key是容器端口/协议,value里HostPort是宿主机端口
RestartPolicy 重启策略,对应--restart,这里no表示不自动重启
Privileged 是否特权模式,对应--privileged

Mounts——挂载详情,比HostConfig.Binds更结构化:Type区分bind(绑定宿主目录)和volume(命名卷),Source是宿主机路径、Destination是容器内路径,RW表示是否可读写。

  • Config——容器自身配置,大多继承自镜像:
字段 含义
Hostname 容器主机名,默认是容器ID前12位
Env 环境变量列表
Cmd 默认启动命令
Entrypoint 入口点,null表示直接用Cmd
Image 镜像名:标签,给人看的那个名字
WorkingDir 工作目录,对应-w
ExposedPorts 镜像EXPOSE声明的端口,只是声明,不等于已映射
Labels 镜像/容器的元数据标签

NetworkSettings——网络运行态:IPAddress是容器IP、Gateway是网关、IPPrefixLen为16即/16掩码、MacAddress是MAC地址;Ports是实际生效的端口映射(HostIp0.0.0.0表示监听宿主机所有网卡);Networks列出容器接入的每个网络,可以有多个。

几个容易混的点,单独记一下:

  • Config.ExposedPorts只是镜像「声明」会用到的端口,并不代表真映射;对外是否通,要看HostConfig.PortBindingsNetworkSettings.Ports
  • 顶层Image(sha256哈希)和Config.Image名:标签)是两个东西,一个给机器、一个给人。
  • 容器没起来时NetworkSettings.IPAddress是空字符串,别指望停止的容器还留着IP。

相关生态

Docker与Podman的关系

Podman(POD MANAGER)是Red Hat主导的开源容器引擎,定位是Docker的替代品。它和Docker都遵循OCI(Open Container Initiative)标准,所以镜像通用——Docker Hub上的镜像Podman照样能拉能跑。两者最大的差异在架构。

Docker是客户端-守护进程模式:docker命令只是客户端,真正干活的是常驻后台、以root运行的dockerd守护进程。这个daemon一旦挂掉,所有容器跟着受影响,而且它常年用root权限运行,是个安全隐患。

Podman是daemonless(无守护进程)设计:没有常驻后台进程,podman命令通过fork-exec直接拉起容器进程,容器就是它的子进程。这带来两个直接好处:

  • Rootless:默认支持普通用户无root运行容器,攻击面更小,权限更安全。
  • 天然适配systemd:容器是普通进程,可以直接交给systemd托管,开机自启、进程监控都顺理成章。

podman命令
podman命令

核心差异对比:

对比项 Docker Podman
架构 C/S,依赖dockerd守护进程 daemonless,无常驻进程
运行权限 守护进程默认以root运行 默认支持rootless(普通用户)
镜像标准 OCI OCI(与Docker完全通用)
命令行 docker ... podman ...,命令几乎一一对应
Pod概念 无(原生不支持) 支持,可把多容器编成一个pod
编排集成 Docker Compose / Swarm podman generate kube生成K8s YAML
默认发行版 各发行版需手动装 RHEL8+、Fedora、CentOS Stream内置

因为命令高度兼容,从Docker迁过来几乎零成本,很多人直接alias docker=podman接着用。Podman还借鉴了Kubernetes的pod概念,能把几个关系紧密的容器编进同一个pod里共享网络,这点比原生Docker更贴近K8s的使用习惯。

一句话记住:Podman是去掉了守护进程、默认更安全的Docker平替。日常单机开发两者体验差别不大;在意安全(rootless)、或要和systemd、Kubernetes打通时,Podman更有优势。


基础篇就记到这里。理解了容器与镜像、装好环境、会用基本命令和docker run/docker inspect之后,再往下就是Docker的网络、数据卷、用Dockerfile构建镜像、镜像分发与Compose编排,这些进阶内容都整理在《Docker学习笔记——进阶篇》