
说话之前先把结论放在前面Docker 刚接触的时候最容易被一堆新名词劝退——镜像、容器、守护进程、数据卷、Compose 编排……听起来像是另一套操作系统。可一旦把工作流程跑通你会立刻发现它不过就是一个装好应用、连环境一起打包的“集装箱”。环境不一致的问题、换机器重新部署的问题、多人协作时“我这是能跑的你那边怎么报错了”的问题全都能压下来。这篇文章就是一份小白向的极速入门笔记按我踩过坑之后的经验顺序来讲不用去看官方文档从头啃顺着命令敲一遍就上手了。1. 先从整体上把握Docker 到底解决了什么问题1.1 用集装箱的比喻来理解核心概念我习惯把 Docker 类比成物流运输里的集装箱。你平时开发一个系统代码只是一个环节真正会坑人的是依赖环境Python 版本、Node 版本、数据库驱动、系统库、环境变量、配置文件。这些东西散落在服务器上换一台机器就得重新折腾一遍。集装箱的做法是把货物连同运输要求、包装规格一起标准化不管船运、卡车还是仓库只要按同一套标准装卸就能直接搬走。Docker 里的“集装箱”就是从代码到运行环境的完整打包单元里面包含了一个应用运行需要的全部东西。别人把这个箱子拿到任何安装了 Docker 的机器上打开跑出来的行为几乎一样。对应关系也很直接镜像Image集装箱本身和里面的全部货物。它是一个只读模板定义了容器运行时的文件系统、依赖、命令。容器Container集装箱被装上车、在真实世界中跑起来的那一刻。镜像是静态的容器是一个镜像实例有状态、可启动、可停止、可删除。仓库Registry存放镜像的地方。你可以把它当做一个公共集装箱堆放场随时拉取想要的镜像到本地。Dockerfile制造集装箱的图纸。上面写了每一步往里面放什么东西执行什么命令最终 build 出镜像。一句话概括镜像就是“能力包”容器就是“运行时”仓库负责“分发”。1.2 为什么小白也能快速上手很多初学者担心 Docker 太底层怕自己驾驭不了。实际上Docker 的日常使用高频操作就那几个拉镜像、跑容器、进容器里看日志、停止和删除容器。只要先把这几个动作练熟你的使用率已经覆盖开发环境部署需求的八成以上。我的个人经验是不要一开始沉迷于网络、存储驱动、内核命名空间这些底层原理。先让它跑起来再逐步理解原理。解决“跑起来”的问题基本只需要掌握五件事怎么安装 Docker。怎么拉取一个镜像。怎么创建一个容器并映射端口。怎么访问运行中的容器。怎么清理不再使用的镜像和容器。这五件事掌握了你已经在用 Docker 做正事了。后面需要写复杂业务时再回头补 Dockerfile、Compose、网络这些进阶能力学习曲线会平缓得多。2. 环境准备把 Docker 快速装好并验证2.1 不同系统下的安装方式Docker 官方提供了桌面版和命令行版本。日常学习和开发建议直接按照你所在系统的官方方式安装。以 LinuxUbuntu/Debian 系为例最常见的方式是sudo apt-get update sudo apt-get install docker.io sudo systemctl enable --now docker如果使用 Docker 官方仓库安装还需要添加软件源和 GPG 密钥但这套流程官网写得非常详细照着执行就行。Windows 和 macOS 用户直接安装 Docker Desktop它有图形界面能帮你管理容器、查看日志对新手尤其友好。安装完成以后先确认一下守护进程是否正常工作docker version这个命令会输出 Client 和 Server 两部分信息。如果只有 Client 信息没有 Server 部分说明 Docker 守护进程没启动或者当前用户的权限不够。Linux 下最常见的情况是当前用户不在 docker 用户组里。解决办法是把当前用户加入 docker 组然后重新登录sudo usermod -aG docker $USER注意加入 docker 组等同于把系统 shell 的高权限交给用户因为 Docker 本质上允许对宿主机进行很多操作。个人开发机这么配没问题但生产服务器上建议严格遵循最小权限原则不是每个人都加入 docker 组。2.2 第一个命令hello-world安装好之后我建议你运行人生中第一个容器用官方那个极小体积的 hello-world 镜像来完成“初跑”验证docker run hello-world这段命令的执行过程其实是本地查找是否有名为 hello-world 的镜像。没找到就从默认仓库拉取。镜像下载成功后基于它创建并启动容器。容器执行预设的启动命令输出一段欢迎信息。输出完成后容器退出。你可能注意到执行完之后容器立刻结束了。很多新手以为容器“退出”就是故障其实不是。容器存在的意义是运行你指定的进程进程结束容器就退出这是正常行为。后面想保持容器一直运行就要让里面的进程持续驻留。2.3 查看当前环境里的基础“存货”环境验证通过后花一分钟看看你手里现在有什么docker images这条命令展示本机已有的镜像包括镜像名、标签、镜像 ID、大小和创建时间。顺手再运行docker ps docker ps -a注意第一条和第二条的区别docker ps只列出正在运行的容器docker ps -a列出所有容器包括已经退出的。新手经常打开docker ps发现是空的以为自己安装失败其实只是当前没有任何运行中的容器而已。这两条命令配合使用是你后面排查问题的基本起点。看到容器在哪个状态、端口映射对不对、运行多长时间了都可以从这里获得第一手信息。3. 核心命令实操从拉镜像到跑容器一次走通3.1 镜像操作拉取、查看、删除拉镜像的命令是docker pull。例如docker pull nginx:latest这里nginx是镜像名latest是标签。标签代表版本latest不一定是稳定版只代表最近一次被标记为最新的版本。追求可复现性时我更建议锁一个具体版本比如docker pull nginx:1.25为什么要锁版本因为三天前和三天后拉同一个latest镜像内容可能完全不一样。你昨天验证线上没问题的镜像今天重新拉可能就把行为改掉了。开发环境随便用没问题部署相关场景一定要锁版本。查看本地镜像上一步已经说过用docker images。删除本地镜像则用docker rmi nginx:1.25如果删除时提示镜像正被某个容器使用需要先删掉对应容器。可以先运行docker ps -a查出来再docker rm删掉容器最后再删除镜像。这个顺序经常被忽略新手被报错搞得很急躁实际上错误信息里已经写得很明白了。3.2 容器操作运行、停止、重启、删除创建一个容器最简单的方式是docker run -d --name my-nginx -p 8080:80 nginx:1.25拆开看这几个参数-d后台运行不要让终端被进程占住。--name my-nginx给容器起一个易识别的名字否则会得到一个随机生成的奇怪名字。-p 8080:80端口映射。宿主机 8080 端口的数据流转到容器内的 80 端口也就是 Nginx 默认监听端口。nginx:1.25要启动的镜像。跑起来之后在浏览器访问http://localhost:8080能看到 Nginx 默认页面恭喜你容器已经在正常工作。停止和删除是另一组操作docker stop my-nginx docker rm my-nginx停止只是把容器终止容器本身和它的写层数据还在可以重新docker start my-nginx启动。删除是彻底移除这个容器实例。还有一个高频操作是重启docker restart my-nginx它先执行 stop再执行 start。和直接跑一个新的容器相比restart 会保留原有容器配置适合临时重启服务。3.3 进入容器内部和执行命令有时你需要进容器里看文件、改配置、调试程序。两个常用命令docker exec -it my-nginx bash-it表示分配一个交互式终端后半段bash是在容器内启动一个 bash 进程。进去之后你就像在一台新机器上操作一样可以ls、cat、ps、tail等非常直观。不过很多精简镜像不带 bash只有 sh。遇到这种情况把命令换成docker exec -it my-nginx sh另一个高频需求是临时在容器里执行某个命令然后马上退出。比如docker exec my-nginx nginx -t这个命令用于验证 Nginx 配置文件语法是否正确适合改了配置后快速自检。提醒一下进入容器改文件改完重启容器容器内文件还在。但是一旦执行docker rm删除容器容器内所有不属于挂载卷的数据全部丢失。所以关键数据一定要挂载到宿主机否则就是拿自己的手办去赌会不会突然清零。3.4 看日志和监控运行状态容器后台运行后你没法直接看到它往外输出的内容。日志要靠命令捞docker logs my-nginx加一个-f参数可以像tail -f那样持续跟踪docker logs -f my-nginx排查容器为什么启动失败第一步永远是先看日志。不要一上来就怀疑网络、怀疑代码先看容器实际输出80% 的问题都能在日志里找到答案。查看容器内部进程状态和资源占用用docker top my-nginx docker statsdocker stats会动态显示所有运行中容器的 CPU、内存、网络和磁盘 I/O 情况。我之前遇到过容器内存悄悄爆掉的问题就是靠docker stats抓出来的。4. 用 Dockerfile 构建属于自己的镜像4.1 理解 Dockerfile 的基本结构前面跑的都是别人做好的镜像实际工作中你迟早要为自己写的项目构建镜像。Dockerfile 就是一份构建说明书每一行指令代表一个构建步骤。拿一个最简单的 Python 项目举例FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD [python, main.py]逐行解读FROM基础镜像所有后续指令都基于它进行。选基础镜像时优先选官方镜像体积越小越好slim版本就是精简版。WORKDIR设置工作目录之后所有操作都在这个路径下执行。COPY把宿主机文件复制进镜像。RUN构建过程中执行命令比如安装依赖。CMD容器启动时运行的命令。它是镜像的默认启动指令。这里有一个容易被忽略的点RUN是在构建镜像时执行CMD是在容器启动时执行两者执行时机完全不同。很多小白在CMD里写安装依赖的操作容器每次启动都重装一遍又慢又不合理。4.2 构建镜像从代码到“集装箱”假设你的项目目录里有上述 Dockerfile在目录下执行docker build -t my-python-app:v1 .-t指定镜像名和标签最后的.指定构建上下文路径也就是当前目录。Docker 会把当前目录下所有文件发给构建进程所以项目里没用的临时文件、敏感配置文件建议提前清理或写进.dockerignore文件。.dockerignore的写法和.gitignore非常相似__pycache__/ *.pyc .git .env data/加了这些排除项之后构建上下文体积会小很多构建速度也能提上来。我见过有人把几百 MB 的数据目录一起打进镜像镜像体积大到离谱后来发现就是漏写了.dockerignore。构建完成后docker images能看到my-python-app出现在列表里。然后运行它docker run --rm my-python-app:v1--rm表示容器退出后立即删除。适合临时测试、执行一次性任务避免留下成堆的终止容器。4.3 镜像分层的思路和减少体积的两个小技巧Dockerfile 的每条指令都会生成一个只读层层叠加起来就是最终镜像。这里有个很有意思的机制如果前面某一层内容没变构建时就能直接使用缓存不重新执行该层后面的操作。想把缓存利用率拉满核心策略是把“不常变的内容”放在 Dockerfile 前面把“经常变的内容”放在后面。比如刚才那份 Dockerfile先拷贝requirements.txt安装依赖再拷贝全部源码。这样做的好处是只要依赖没变即使源码改了一百次依赖层也永远不会重新安装构建速度飞快。减少体积我个人比较推荐两个方案使用体积更小的基础镜像比如python:3.11-slim、alpine系列。在同一层里执行安装、清理、删除临时文件的操作避免中间产物残留。不过 alpine 虽然有体积优势某些需要编译原生依赖的项目会带来额外麻烦需要权衡取舍。不是所有项目都适合无脑换 alpine。5. 数据卷与容器端口映射5.1 为什么容器里的数据不能随便放容器是一个隔离环境删除容器时容器文件系统里一切不属于镜像内容的数据都会消失。这相当于你打开一个记事本写了半天关掉窗口时忘记保存再打开发现全没了。为了解决这个问题Docker 提供了数据卷Volume和绑定挂载Bind Mount两种方式。简单理解把宿主机上的某个路径映射到容器内的某个路径容器里写入的数据实际落在宿主机上。容器删除后数据还在。5.2 卷挂载的两种方式命名卷是最推荐的方式docker volume create my-data docker run -d --name my-db -v my-data:/var/lib/mysql mysql:8.0这里的-v my-data:/var/lib/mysql把命名卷my-data挂载到容器内的 MySQL 数据目录。就算删除容器只要卷还在重新创建一个容器挂载同一个卷数据依然完整。绑定挂载则更直接把宿主机某个目录映射进去docker run -d --name my-web -v /home/user/www:/usr/share/nginx/html:ro nginx:1.25:ro表示只读挂载宿主机目录里的文件容器只能读不能改。这个模式很适合前端静态文件宿主机改了文件容器里马上同步生效连重新构建都不用开发调试极方便。5.3 理解端口映射Docker 容器默认有自己独立的网络命名空间宿主机访问不到容器内部 IP除非把端口映射出来。上面反复用到的-p参数就是干这件事的。-p 8080:80有一个隐含概念需要说明冒号前面是宿主机端口后面是容器端口。宿主机端口冲突会直接报错换一个宿主机端口就行-p 8081:80就是另一个映射。如果不加任何-p容器其实还是能跑起来只是外部完全访问不到它的服务只能通过docker exec进入容器内部调试。查看当前映射关系docker port my-nginx它会输出类似80/tcp - 0.0.0.0:8080的结果一眼就能确认端口有没有映射错。6. 用 docker-compose 管理一套服务6.1 为什么单个 docker run 不够用实际项目很少只靠一个容器跑起来。你可能需要应用容器、Redis、MySQL、Nginx四个容器一起配合。如果用docker run一条条敲每次启动都要重复执行四遍命令参数一多还容易漏。docker-compose 的解决方案是把整套服务的配置写进一个docker-compose.yml文件一键启动、一键停止。6.2 一个最小可用的 Compose 配置下面是一个典型的 Web 应用加数据库组合services: app: build: . ports: - 8000:8000 depends_on: - db environment: DB_HOST: db DB_PORT: 5432 db: image: postgres:16 environment: POSTGRES_USER: app_user POSTGRES_PASSWORD: app_pass POSTGRES_DB: app_db volumes: - pg-data:/var/lib/postgresql/data volumes: pg-data:配置里定义了app和db两个服务。app通过build指定使用当前目录的 Dockerfile 构建db直接使用 postgres 镜像。environment里的DB_HOST: db有个关键逻辑在同一 Compose 网络下服务名就是容器之间的主机名。app里访问数据库时不写 IP写服务名db即可。启动整套服务docker-compose up -d查看状态docker-compose ps停止整套服务docker-compose down加上-v参数时会连卷一起删除docker-compose down -vdown -v会把数据库数据一并清空新手常在测试环境里误操作把里面积累的数据忽然全清掉。如果只是想停掉服务但保留数据只敲docker-compose down就够了。6.3 构建、日志、重启与扩展Compose 中比较实用的几个操作docker-compose up -d --build这个命令先重新构建镜像再启动容器代码有变更时最常用。看某个服务的日志docker-compose logs -f app这条命令只跟踪app服务的日志不会一股脑输出所有服务的日志。重启某个服务docker-compose restart app如果临时需要一个服务多实例跑Compose 还支持直接指定副本数量不过这个用法推荐的场景比较有限大多数服务是无状态的才能随便扩。7. 小白最容易踩的坑和排查笔记7.1 端口冲突容器起来了但访问失败有一次我在本机跑项目docker run执行成功docker ps也在运行列表里但浏览器访问对应端口就是不通。排查到最后发现这个端口之前被另一个容器占用了新容器的映射参数虽然写着 8080实际上启动时已经因为绑定失败退出了。只是我用了-d后台运行没注意到报错信息。排查思路分享给你先看docker ps -a确认容器是否还在运行状态里有没有Exited。用docker logs 容器名看日志报错多半在这里。用ss -tlnp或netstat -tlnp查看宿主机端口占用情况。如果是旧容器残留直接docker rm删掉再重跑。7.2 时区问题容器里明明什么都有时间却不对很多官方镜像默认使用 UTC 时区容器里date看到的时间和宿主机相差八小时。应用打印的日志时间里全偏差排查问题时容易造成误解。解决方式有两种。第一种简单粗暴在运行参数里加时区环境变量docker run -e TZAsia/Shanghai ...第二种在 Compose 配置里同样加上environment: - TZAsia/Shanghai。如果镜像内部有更复杂的时区机制可能还需挂载宿主机的/etc/localtime文件我常用的是-v /etc/localtime:/etc/localtime:ro7.3 容器一启动就退出别慌刚接触时我经常写错启动命令容器启动不到一秒就退出了。日志也没输出多少内容看起来什么都没发生。这里有一个判断思路如果你的进程本身就是要长期驻留的服务比如 Nginx、MySQL它退出说明启动失败去看日志。如果你的进程是执行一次性任务的脚本比如备份、定时任务它运行完退出是正常行为。想故意让容器保持运行以便调试可以临时覆盖启动命令比如docker run -it --rm my-image tail -f /dev/nulltail -f /dev/null会阻塞住让容器一直存活。这个方法在调试镜像内环境时特别好用进去慢慢摸清问题。7.4 镜像体积过大与构建缓存失效这两个问题往往一起出现。体积过大通常是因为构建上下文里包含了大量不需要的文件解决办法是写.dockerignore。构建缓存频繁失效则是因为 Dockerfile 里经常变动的内容放在了太靠前的位置把依赖安装步骤挪到源码拷贝之前缓存复用率就会显著提升。如果发现构建时任何一层都缓存不了可以试试docker build --no-cache -t my-image .强制不使用旧缓存用作一次干净构建来排查问题。不过这个命令会慢很多适合怀疑缓存异常时临时用。7.5 清理孤儿容器和悬空镜像本地实验多了以后磁盘上会积累大量停止的容器、没有被任何容器引用的悬空镜像以及残留的构建缓存。定期清理是我的个人建议docker container prune docker image prunedocker container prune会删除所有已停止的容器docker image prune会清理所有悬空镜像没有被容器引用的镜像。如果还想清理得更彻底一点可以加-a参数把未被任何容器使用的镜像也一并删除但开这个参数前一定自己想清楚有些镜像可能还要用。最后分享一个我个人的操作习惯新建一个项目目录第一件事就是把Dockerfile和docker-compose.yml这两个文件一并建好哪怕前期只有一个服务也这么干。不是因为我一开始就懂编排而是后续每加一个中间件、每换一个版本改动只需要落在配置里重新拉起就能继续工作比在脑子里记住一串命令靠谱得多。等到你熟练到能一眼看懂docker ps的输出、能从日志里快速定位问题就会发现 Docker 并不难难的是没遇到一套顺手的入门路径。这篇笔记把我当初最希望有人提前告诉我的内容都写了出来照着操作一遍剩下的就交给时间积累。