Docker 这东西,刚接触的人容易走两个极端:要么觉得它就是个"轻量级虚拟机",装完跑个 hello-world 就搁那儿了;要么被一堆镜像、容器、数据卷、网络的名词吓退,觉得这是运维才碰的东西。我当初属于前者,装完 Docker 之后大半年没动过,直到有次要在本机同时跑三个不同版本的数据库做兼容测试,装一台卸一台折腾到半夜,才真正意识到容器化解决的就是这种"环境打架"的破事。
这篇内容面向的是刚把 Docker 装好、想拿它干点实事的人。我会从"为什么值得用"讲到"怎么把常用软件一个个跑起来",中间穿插我自己踩过的坑和参数选择的理由。读完你应该能做到:用几条命令把 MySQL、Redis、Nginx、PostgreSQL 这类常用服务拉起来,知道数据往哪儿放、端口怎么映射、容器挂了怎么排查,而不是每次出问题就docker rm -f重来。关键词就围绕 Docker 安装、镜像、容器、端口映射、数据卷、常用软件部署这些展开,都是实操里绕不开的东西。
1. 先把"为什么用 Docker"这件事想明白
很多人学 Docker 是被动的——公司要求、教程推荐、面试要问。但如果你不清楚它到底解决了什么问题,学起来就是死记命令,遇到变体就懵。我在带新人的时候发现,凡是能说清楚"容器和虚拟机差在哪"的人,后面上手都快得多,因为他们知道边界在哪,不会拿 Docker 去干它不擅长的事。
1.1 容器不是虚拟机,这个区别决定了你的使用方式
最容易被误解的一点:Docker 容器里跑的不是一个完整的操作系统。虚拟机是在物理机上虚拟出硬件,再装一整套 Guest OS,启动要几十秒,内存动辄分走 1-2G;而容器是共享宿主机内核,靠命名空间做隔离、靠 cgroups 做资源限制,本质上还是宿主机上的一批进程,只是被"关"在了一个看起来独立的环境里。
这个区别带来的实际影响很直接。第一,容器启动是秒级甚至毫秒级的,因为它不需要引导系统;第二,容器里的"操作系统"其实只是镜像里打包的那点用户态文件,内核用的是宿主机的,所以你在容器里uname -r看到的版本和宿主机一致;第三,也是最重要的一点——容器不适合跑需要独立内核模块的东西,比如你要在容器里加载某个特定的内核驱动,基本行不通。
我见过有人试图在容器里跑需要特定内核参数的服务,折腾半天发现改不了,最后还得回到虚拟机。所以用之前先问自己:我要跑的东西依赖内核吗?如果只是应用层服务(Web 服务、数据库、缓存、消息队列),容器完全够用;如果涉及内核调优、特殊驱动,老老实实用虚拟机或者物理机。
1.2 镜像和容器的关系,用"类与实例"理解最省事
镜像(image)是只读的模板,容器(container)是镜像跑起来的实例。这个类比虽然老套但准确:一个镜像可以起无数个容器,每个容器有自己的可写层,互不干扰。你在容器里改的文件,除非提交成新镜像或者挂到数据卷,否则容器一删就没了。
理解这一点能帮你避开一个高频错误:别把重要数据放在容器的可写层里。我早期就干过这事,在一个 MySQL 容器里建了库、导了数据,觉得跑得好好的,结果有次升级镜像版本,顺手docker rm了旧容器,数据全没了。后来才养成习惯——凡是需要持久化的东西,一律挂数据卷或者绑定宿主机目录,容器本身当成"一次性"的来对待。
镜像的分层机制也值得提一句。Dockerfile 里每条指令都会生成一层,层是复用的。所以你把不常变的部分(比如装依赖)写在前面、常变的部分(比如拷贝代码)写在后面,构建时能命中缓存,速度快很多。这个原理在后面讲自己构建镜像时会用到。
1.3 什么场景下 Docker 真正省事
不是所有场景都适合上 Docker。我总结下来,下面这几类用它收益最明显:
- 本机要跑多个版本的同一种服务:比如同时要 MySQL 5.7 和 8.0 做兼容测试,装两台数据库互相冲突,用容器各起一个,端口错开就行。
- 快速试用某个软件:想试试 Redis、RabbitMQ、MinIO 这些,不想污染本机环境,一条
docker run起来,用完docker rm删掉,干干净净。 - 统一开发和生产环境:把依赖打包进镜像,避免"我本地能跑"的经典问题。不过这个要配合 CI 才发挥最大价值,单机玩意义有限。
- 隔离有风险的程序:跑一些来源不明的脚本或者老版本软件,扔容器里跑,出问题删掉即可。
反过来,如果你只是装个单机小工具、对隔离没要求、也不想学额外概念,直接装本机可能更省心。Docker 有学习成本,别为了用而用。
2. 安装这一步,选对方式比装得快更重要
安装本身不难,难的是选对安装方式,以及装完之后把权限、镜像源这些配套的东西配好。我见过太多人卡在"装完了但每次都要 sudo"或者"拉镜像慢到怀疑人生"上,其实都是安装阶段没处理干净。
2.1 不同系统下的安装路径选择
Linux 下我强烈建议用官方仓库装,而不是系统自带的包管理器版本。原因很实际:发行版仓库里的 Docker 版本往往落后好几个大版本,一些新特性(比如 compose v2 的语法、buildkit 的默认启用)用不上,而且出问题时社区文档对不上。以常见的 Debian/Ubuntu 系为例,标准流程是先卸掉可能存在的旧版本,再添加官方 GPG key 和仓库,最后安装docker-ce。
# 卸载可能存在的旧版本 sudo apt-get remove docker docker-engine docker.io containerd runc # 安装依赖 sudo apt-get update sudo apt-get install ca-certificates curl gnupg # 添加官方 GPG key sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \ sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg # 添加仓库 echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 安装 sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginmacOS 和 Windows 就直接装 Docker Desktop,图形化界面省心,内置了 compose 和 buildx。Windows 上要注意开启 WSL2 后端,性能比 Hyper-V 后端好不少,尤其是文件系统 IO。装完之后在设置里把资源限制调一下,默认给的内存可能偏小,跑数据库会吃力。
提示:安装完先跑
docker version和docker info,确认客户端和服务端都正常。如果docker info报 "Cannot connect to the Docker daemon",说明服务没起来,Linux 下用sudo systemctl start docker启动并sudo systemctl enable docker设为开机自启。
2.2 免 sudo 和镜像加速,两个必做的收尾
装完第一件事是把当前用户加进 docker 组,否则每条命令都要 sudo,烦不说,还容易在脚本里出权限问题。
sudo usermod -aG docker $USER # 退出重新登录,或者 newgrp docker 让组生效这里有个安全提醒得说清楚:docker 组的权限等价于 root,因为能起容器就能挂载宿主机目录、能提权。所以这个操作只在自己可控的开发机上做,多人共用的服务器上要谨慎,别随便给人加。
第二件事是配镜像加速。默认从官方仓库拉镜像,国内网络环境下经常慢到超时。配一个可用的镜像源能省大量时间。编辑/etc/docker/daemon.json:
{ "registry-mirrors": ["https://你的镜像源地址"], "log-driver": "json-file", "log-opts": { "max-size": "100m", "max-file": "3" } }改完sudo systemctl daemon-reload && sudo systemctl restart docker生效。顺便把日志轮转也配上,不然容器跑久了日志文件能把磁盘撑爆——这个坑我踩过,一个没配日志限制的服务跑了两个月,日志干到 40 多个 G,磁盘直接告警。
2.3 验证安装:别只跑 hello-world
docker run hello-world能跑通只说明最基本的链路通了。我一般会多做两步验证:一是拉一个带完整工具链的镜像跑个交互式 shell,确认网络和文件系统正常;二是跑一个带端口映射的服务,确认端口转发没问题。
# 验证基础运行 docker run --rm hello-world # 验证交互和网络 docker run --rm -it alpine sh -c "apk add curl && curl -s https://example.com | head -n 1" # 验证端口映射 docker run -d --name test-nginx -p 8080:80 nginx:alpine curl -I http://localhost:8080 docker rm -f test-nginx三步都过,说明安装这块彻底没问题了,可以往下走。
3. 常用软件的容器化部署:从数据库到中间件
这部分是重头戏。我把日常最常打交道的几类软件按"起容器时要注意什么"来拆,每个都给一条能直接抄的命令,再解释参数为什么这么写。注意所有命令里的密码、端口都是示例,实际用的时候换成自己的。
3.1 MySQL:数据卷和字符集是两个必配项
MySQL 容器化最常被问的就是"数据会不会丢"。答案取决于你有没有挂数据卷。下面这条命令是我常用的模板:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=YourStrongPass123 \ -e MYSQL_DATABASE=appdb \ -e TZ=Asia/Shanghai \ -v mysql8-data:/var/lib/mysql \ -v /etc/localtime:/etc/localtime:ro \ --restart unless-stopped \ mysql:8.0 \ --character-set-server=utf8mb4 \ --collation-server=utf8mb4_unicode_ci逐项说下理由。-v mysql8-data:/var/lib/mysql用的是命名卷,Docker 会在/var/lib/docker/volumes/下管理,比绑定宿主机目录省心,权限问题少。如果你需要直接访问数据文件做备份,可以改成绑定目录如-v /data/mysql8:/var/lib/mysql,但要确保宿主机目录的属主和容器内 mysql 用户的 UID 对得上,否则启动会报权限错误——这是新手最常卡的地方。
字符集参数放在镜像名后面,是传给 mysqld 的启动参数。MySQL 8 默认字符集已经是 utf8mb4 了,但显式指定一遍没坏处,尤其是从 5.7 迁移过来的时候。--restart unless-stopped让容器在宿主机重启后自动拉起,除非你手动 stop 过它,这个策略比always更符合直觉。
注意:
MYSQL_ROOT_PASSWORD只在数据卷为空的首次初始化时生效。如果你改了密码环境变量但卷里已有数据,新密码不会生效,得进容器用ALTER USER改。这个机制坑过不少人,以为是环境变量没传对。
3.2 Redis:持久化和密码别偷懒
Redis 起容器简单,但生产向的配置要考虑持久化和访问控制:
docker run -d \ --name redis7 \ -p 6379:6379 \ -v redis7-data:/data \ --restart unless-stopped \ redis:7-alpine \ redis-server --appendonly yes --requirepass YourRedisPass--appendonly yes开启 AOF 持久化,数据落在/data下,配合命名卷就能持久。不开启的话重启容器数据就没了,只适合纯缓存场景。--requirepass设密码,别裸奔——我见过把 6379 直接映射到公网的,几天就被扫到并写入了挖矿脚本的定时任务,教训很深刻。
alpine 版本的镜像体积小(几十 M),启动快,日常够用。如果要用到一些需要编译的模块,再换完整版镜像。
3.3 Nginx:配置文件挂载的正确姿势
Nginx 容器化最常见的需求是改配置。有两种做法:一是把配置COPY进自定义镜像,二是把配置目录挂载进容器。开发调试阶段用挂载更灵活,改完docker exec nginx nginx -s reload就生效,不用重建镜像。
docker run -d \ --name nginx \ -p 80:80 -p 443:443 \ -v /data/nginx/conf.d:/etc/nginx/conf.d:ro \ -v /data/nginx/html:/usr/share/nginx/html:ro \ -v /data/nginx/logs:/var/log/nginx \ --restart unless-stopped \ nginx:stable-alpine配置目录用:ro只读挂载,防止容器内进程误改宿主机文件。日志目录单独挂出来,方便在宿主机上直接看和做轮转。这里有个细节:如果你挂载的conf.d目录是空的,Nginx 会因为没有 server 块而启动失败或者只跑默认页,记得先放一个.conf文件进去。
3.4 PostgreSQL 和消息队列:参数思路是相通的
PostgreSQL 的起法和 MySQL 类似,只是环境变量名不同:
docker run -d \ --name pg16 \ -p 5432:5432 \ -e POSTGRES_PASSWORD=YourPgPass \ -e POSTGRES_DB=appdb \ -e TZ=Asia/Shanghai \ -v pg16-data:/var/lib/postgresql/data \ --restart unless-stopped \ postgres:16-alpineRabbitMQ 带管理界面的话,多映射一个 15672 端口:
docker run -d \ --name rabbitmq \ -p 5672:5672 -p 15672:15672 \ -e RABBITMQ_DEFAULT_USER=admin \ -e RABBITMQ_DEFAULT_PASS=YourMqPass \ -v rabbitmq-data:/var/lib/rabbitmq \ --restart unless-stopped \ rabbitmq:3-management-alpine你会发现套路高度一致:映射端口、传环境变量做初始化、挂数据卷做持久化、设重启策略。把这四件事想清楚,任何有官方镜像的软件都能照葫芦画瓢跑起来。真正需要单独研究的是各软件自己的初始化参数和目录结构,这个查官方镜像文档最准。
4. 端口、数据卷、网络:三个绕不开的机制
前面命令里反复出现-p、-v、--name,这些不是随便加的。这一节把这三个机制单独拎出来讲透,因为后面排查问题时,八成的问题都出在这三块。
4.1 端口映射的格式和冲突处理
-p 宿主机端口:容器端口是最常见的形式。还有几种变体值得知道:
| 写法 | 含义 | 适用场景 |
|---|---|---|
-p 8080:80 | 宿主机 8080 映射到容器 80 | 最常用,明确指定 |
-p 127.0.0.1:8080:80 | 只绑定本机回环 | 不想暴露到局域网 |
-p 80 | 宿主机随机端口映射到容器 80 | 避免端口冲突,用docker port查 |
-p 8080:80/udp | UDP 端口 | DNS、部分游戏服务 |
端口冲突是最常见的启动失败原因。报错长这样:Bind for 0.0.0.0:3306 failed: port is already allocated。意思是宿主机 3306 已经被占了,可能是本机装了 MySQL,也可能是另一个容器。排查用sudo lsof -i :3306或者docker ps看有没有容器占着。解决要么停掉占用方,要么换个宿主机端口。
还有个容易忽略的点:容器之间通信不需要走宿主机端口映射。同一个自定义网络里的容器,直接用容器名当主机名、用容器内部端口互访就行。端口映射主要是给宿主机和外部访问用的。
4.2 命名卷和绑定挂载,什么时候用哪个
数据持久化有两条路:命名卷(named volume)和绑定挂载(bind mount)。两者的区别我用一个表说清楚:
| 维度 | 命名卷 | 绑定挂载 |
|---|---|---|
| 管理方 | Docker 管理,存在/var/lib/docker/volumes/ | 你自己指定的宿主机路径 |
| 权限问题 | 少,Docker 处理 | 多,需自己对 UID/GID |
| 可移植性 | 好,跨机器迁移方便 | 依赖宿主机目录结构 |
| 直接访问文件 | 需进目录或临时容器 | 直接可见可编辑 |
| 适合场景 | 数据库数据、应用数据 | 配置文件、开发时代码热更新 |
我的习惯是:数据用命名卷,配置用绑定挂载。数据库数据放命名卷,省心;Nginx 配置、应用代码放绑定挂载,方便改。这个组合兼顾了安全和灵活。
命名卷的常用操作:
docker volume ls # 列出所有卷 docker volume inspect mysql8-data # 看卷的详细信息,包括挂载点 docker volume rm mysql8-data # 删除卷(容器在用时会报错) docker volume prune # 清理未被使用的卷,慎用docker volume prune要特别小心,它会删掉所有没被容器引用的卷。有次我停了一个测试容器,顺手 prune,结果把那个容器的数据卷也清了,数据找不回来。生产环境上这个命令我基本不碰。
4.3 自定义网络让容器互访变得简单
默认情况下容器都连在 bridge 网络上,但默认 bridge 网络有个限制:容器之间只能用 IP 互访,不能用容器名做 DNS 解析。所以稍微复杂点的场景,我都会先建一个自定义网络:
docker network create app-net # 起容器时指定网络 docker run -d --name mysql8 --network app-net ... mysql:8.0 docker run -d --name app --network app-net ... myapp:latest这样app容器里直接用mysql8作为主机名就能连到数据库,不用关心 IP。IP 在容器重启后可能变,但容器名在自定义网络里是稳定的 DNS 记录。这个特性在写应用配置时特别有用,连接串里写jdbc:mysql://mysql8:3306/appdb就行,换环境不用改。
排查网络问题常用这几条:
docker network ls # 列出网络 docker network inspect app-net # 看网络里有哪些容器、IP 分配 docker exec -it app ping mysql8 # 从容器内测试连通性 docker exec -it app cat /etc/hosts # 看 DNS 解析结果如果ping不通,先确认两个容器在同一个网络里,再看防火墙或者容器内服务是否真的在监听。
5. 日常运维:日志、进入容器、清理,这些动作天天用
容器跑起来只是开始,日常打交道最多的是看日志、进容器排查、清理垃圾。这几个动作看着简单,但用不对会浪费大量时间。
5.1 看日志的正确方式
docker logs是最常用的命令,但直接docker logs 容器名在日志量大时会刷屏。几个实用参数:
docker logs -f --tail 100 mysql8 # 实时跟踪,只看最后 100 行 docker logs --since 10m nginx # 看最近 10 分钟的日志 docker logs --since 2024-01-01T00:00:00 --until 2024-01-01T01:00:00 nginx # 按时间范围-f是 follow,类似tail -f。--tail控制初始显示行数。这两个组合起来是日常排查的标配。如果日志已经配了轮转(前面 daemon.json 里配的),就不用担心文件无限增长。
有个细节:docker logs只能看到容器主进程写到 stdout/stderr 的内容。如果应用把日志写到文件里(比如 Nginx 的 access.log),docker logs是看不到的,得进容器cat或者把日志目录挂载出来在宿主机看。这也是为什么前面 Nginx 的例子里我把日志目录挂出来了。
5.2 进容器排查:exec 和 attach 的区别
docker exec -it 容器名 bash是进容器最常用的方式,开一个新的进程进去,退出不影响容器主进程。docker attach则是attach到主进程的 stdin/stdout,退出时可能把主进程也带停,日常排查基本不用它。
docker exec -it mysql8 bash # 进容器 docker exec -it mysql8 mysql -uroot -p # 直接在容器里执行命令 docker exec mysql8 env # 看环境变量 docker exec mysql8 ps aux # 看容器内进程如果容器里没有 bash(比如 alpine 镜像默认只有 sh),把bash换成sh。如果连 sh 都没有(极简镜像),可以用docker cp把排查工具拷进去,或者临时起一个带工具的容器用--pid=container:xxx共享命名空间来排查。
5.3 清理:磁盘空间是怎么被吃掉的
Docker 用久了磁盘会莫名其妙变满,主要是三类东西:停止的容器、悬空镜像(dangling image,没有 tag 的中间层)、没用的卷和构建缓存。查看占用:
docker system df # 总览 docker system df -v # 详细到每个镜像/容器/卷清理要分级来,别一上来就docker system prune -a,那会把所有没在跑的镜像都删了,下次用还得重新拉。我的清理顺序是:
docker container prune # 删停止的容器 docker image prune # 删悬空镜像 docker builder prune # 删构建缓存 docker volume prune # 删无用卷(慎用!确认没有要保留的数据)docker system prune是上面几个的合集,加-a会连所有未使用镜像一起删。我一般只在磁盘告急时用,平时按需清理单项。
6. 几个真实踩过的坑和对应的排查思路
光讲命令不够,实际用起来问题千奇百怪。这一节挑几个我印象深的坑,把排查链路完整写出来,你可以照着这个思路去定位自己的问题。
6.1 容器起来了但连不上:分层排查法
现象:docker ps显示容器 Up,但应用连不上。这种问题我按从外到内的顺序排查:
- 端口有没有映射对:
docker port 容器名看映射关系,确认宿主机端口和容器端口都对。 - 宿主机能不能通:
curl localhost:端口或telnet localhost 端口,不通说明映射或防火墙有问题。 - 容器内服务在不在监听:
docker exec 容器名 netstat -tlnp(或ss -tlnp),看服务是否真的在容器内监听了预期端口。有些服务默认只监听 127.0.0.1,容器外就访问不到,得改成 0.0.0.0。 - 容器内能不能自连:
docker exec 容器名 curl localhost:端口,能通说明服务正常,问题在映射层。 - 网络和防火墙:宿主机防火墙(firewalld/ufw)有没有放行端口,云服务器还有安全组。
这个顺序的逻辑是"从最外层往里剥",每步缩小范围。大部分问题卡在第 3 步——服务监听地址不对,这个在配置文件里改一下就好。
6.2 数据卷权限问题:UID 对不上
现象:MySQL 或 PostgreSQL 容器启动失败,日志里报Permission denied或者chown: changing ownership。根因是绑定挂载的宿主机目录属主和容器内进程的 UID 不一致。
容器里的 mysql 用户 UID 通常是 999,postgres 是 999 或 70(看版本)。如果宿主机目录是 root 所有,容器内进程就没权限写。解决办法有两个:一是sudo chown -R 999:999 /data/mysql8把目录给对应 UID;二是干脆用命名卷,Docker 会自动处理权限。我现在的做法是能用命名卷就用命名卷,确实需要绑定目录时,先查清楚镜像里进程的 UID 再设权限。
查镜像里进程 UID 的方法:
docker run --rm --entrypoint id mysql:8.0 # 输出类似 uid=999(mysql) gid=999(mysql)6.3 容器时间不对:时区没传进去
现象:容器里date显示的是 UTC 时间,日志时间戳和本地差 8 小时。容器默认用 UTC,需要显式传时区。两种做法:设TZ环境变量,或者挂载宿主机的/etc/localtime。我一般两个都做,双保险:
-e TZ=Asia/Shanghai -v /etc/localtime:/etc/localtime:ro有些应用(比如 Java)还认-Duser.timezone参数,光设 TZ 不够,得在启动参数里也指定。这个要看具体软件,排查时先确认容器内date对不对,再看应用日志的时间戳。
6.4 镜像拉不下来:先分清是网络还是源的问题
拉镜像卡住或报错,先看错误信息。如果是TLS handshake timeout或connection reset,多半是网络到镜像源的问题,检查 daemon.json 里的镜像源配置有没有生效(docker info能看到 Registry Mirrors 列表)。如果是manifest unknown或not found,那是镜像名或 tag 写错了,去官方仓库确认一下。
还有个隐蔽的坑:镜像源配了但没重启 Docker 服务,配置不生效。改完 daemon.json 一定要systemctl restart docker,然后用docker info确认配置真的加载了。
7. 从单容器到 compose:什么时候该升级
单条docker run命令适合临时试用和简单服务。但当你需要同时起多个有依赖关系的容器(比如应用 + 数据库 + 缓存),一条条敲命令就低效了,而且参数容易记错。这时候该上 Docker Compose。
7.1 compose 解决的是"编排"问题
Compose 用一个 YAML 文件描述多个服务,一条docker compose up -d全部拉起,down全部停止。它自动创建一个网络让服务间用服务名互访,还能定义依赖顺序、环境变量、卷。一个典型的compose.yaml:
services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: YourStrongPass123 MYSQL_DATABASE: appdb TZ: Asia/Shanghai volumes: - mysql-data:/var/lib/mysql ports: - "3306:3306" restart: unless-stopped redis: image: redis:7-alpine command: redis-server --appendonly yes --requirepass YourRedisPass volumes: - redis-data:/data restart: unless-stopped app: image: myapp:latest depends_on: - mysql - redis environment: DB_HOST: mysql REDIS_HOST: redis ports: - "8080:8080" restart: unless-stopped volumes: mysql-data: redis-data:注意app里连数据库用的是mysql这个服务名,因为 compose 默认建的网络里服务名就是 DNS 名。depends_on只保证启动顺序,不保证依赖服务"就绪",应用侧还是要做重试逻辑,这点很多人误解。
7.2 从 run 迁移到 compose 的实用建议
不用一上来就全用 compose。我的建议是:单个服务用docker run快速验证,验证通了再写进 compose 文件固化下来。这样调试阶段灵活,稳定后又能一键复现。
迁移时把docker run的参数对应到 YAML 里:-p对应ports,-v对应volumes,-e对应environment,--name对应服务名,--restart对应restart。命令里镜像名后面的启动参数对应command。对应关系理清了,迁移就是机械翻译。
常用命令:
docker compose up -d # 后台启动所有服务 docker compose ps # 看服务状态 docker compose logs -f app # 看某个服务日志 docker compose down # 停止并删除容器和网络(卷默认保留) docker compose down -v # 连卷一起删(数据会丢,慎用)down默认保留命名卷,所以数据不会丢;加-v才会删卷。这个设计挺合理,日常重启用down再up就行。
8. 一些让日常使用更顺手的习惯
最后分享几个我用了很久、确实提升效率的小习惯,都是些不起眼但天天用得上的东西。
给容器起有意义的名字。--name不是必须的,但不加的话 Docker 会随机生成一个像vigilant_turing这样的名字,后面exec、logs的时候根本记不住。养成起名的习惯,比如mysql8-dev、redis-cache,一眼知道是干嘛的。
善用--rm跑一次性任务。临时跑个命令、试个工具,加--rm让容器退出后自动删除,不留垃圾。比如docker run --rm -it alpine sh,用完退出就没了。
把常用命令做成别名。我在.bashrc里放了几个:
alias dps='docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"' alias dlog='docker logs -f --tail 100' alias dex='docker exec -it'dps这个格式化输出比默认的宽表格清爽很多,一眼看到名字、状态、端口。
定期docker system df看一眼。不用天天清,但隔段时间看看磁盘占用,心里有数。尤其是构建镜像多的机器,缓存能占几十 G。
镜像 tag 别用 latest。latest是个浮动标签,今天拉的和下个月拉的可能是不同版本,出问题很难复现。生产环境一定用具体版本号,比如mysql:8.0.36,升级时显式改版本,可控。
这些习惯单看都是小事,但积累下来能省掉大量"这容器是干嘛的""磁盘怎么又满了""怎么和上次跑的不一样"的困惑。Docker 的学习曲线其实不陡,真正拉开差距的是这些日常细节的积累——知道每个参数为什么这么写,出了问题知道从哪查,比背一堆命令有用得多。