简介一份面向初学者的Docker基础入门指南聚焦容器技术的核心概念与实操技能适合有一定计算机基础、希望通过容器实现快速部署和环境一致的开发者。内容从Docker与虚拟机的差异讲起详细说明镜像作为只读模板、容器作为运行实例、仓库用于存储镜像的三大核心对象并分别介绍在Windows、macOS和Ubuntu以Ubuntu为例上的安装步骤。基础操作部分覆盖拉取镜像、运行容器、查看容器状态、进入容器内部以及停止删除容器等常用命令实战环节通过部署Nginx服务器演示Web应用容器化再以编写Dockerfile构建Python应用镜像展示自定义镜像流程并附权限设置、配置国内镜像源、数据卷持久化等常见问题解决方案。资源含1个PDF文档仅约696KB内容紧凑便于随时查阅。目前已有126人学习容器零基础者可按章节顺序动手实践后续可进一步学习多容器编排与生产环境部署。1. Docker 入门指南把“在我机器上能跑”变成“到哪都能跑”每个做开发和运维的人大概率都经历过同一场尴尬代码在本地跑得好好的发到测试环境就崩崩的原因还不是逻辑错了而是环境不对。某个系统库版本不一样、某个依赖没装、某个配置文件路径写死。这时候你会觉得软件最脆弱的部分不是代码本身而是代码赖以运行的整套环境。Docker 就是冲着这个问题去的——把应用连同它的运行环境一起打包成一个标准单元这个单元在谁的机器上跑行为都应该一样。Docker基础入门指南要解决的正是“我知道 Docker 很火但不知道从哪下手”的处境。适合谁刚接触容器概念的开发者、被环境问题折磨的测试和运维、需要统一交付物格式的项目团队都算。这篇笔记会沿着安装、镜像、容器、数据、排错这条线往下走每一段都是可以直接照着敲的命令读完你能自己把第一个容器跑起来也能看懂别人项目里的 Dockerfile 和启动脚本在做什么。2. 先把环境立起来安装、验证和第一个容器2.1 安装三步走装引擎、配权限、验证守护进程不同操作系统装 Docker 的方式差别不小但核心三步是固定的装引擎、把当前用户加进 docker 组、验证 docker 命令能通。以最常见的 Linux 环境为例我一般会先检查系统里有没有残留的旧版本# 检查是否已安装过 docker which docker || echo not installed # 如果有旧版本先卸载干净再装新的 sudo apt-get remove docker docker-engine docker.io containerd runc接着添加官方源并安装引擎。这一步用包管理器别手动下载二进制因为包管理器会帮你处理好依赖关系和后续升级# 添加 Docker 官方软件源Debian/Ubuntu 系 sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg 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 ar /etc/apt/keyrings/docker.gpg参数说明install -m 0755 -d 是在创建 keyrings 目录并指定权限0755 表示所有用户可读、仅 root 可写gpg --dearmor 是把 GPG 公钥从 ASCII 格式转成二进制格式apt 只认这种格式。写完源列表后再执行安装sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugindocker-ce 是社区版引擎containerd.io 是容器运行时docker-compose-plugin 后面编排多容器时要用。装完之后别急着 sudo docker ps先做权限配置# 把当前用户加入 docker 组之后执行 docker 命令不用再加 sudo sudo usermod -aG docker $USER newgrp docker最后验证守护进程状态。注意usermod 之后要重新登录终端组权限才会真正生效。如果执行docker version报权限错误多半是这一步没重新登录docker version docker psdocker version 能看到 client 和 server 两段信息。client 是本地的命令行工具server 是后台守护进程 dockerd。如果 server 段报错或看不到说明引擎没起来或权限不对这条命令就是后续所有排错的第一道检查点。2.2 第一次跑容器hello-world 到底发生了什么环境装好后的第一个动作惯例是跑一个 hello-world 镜像docker run hello-world这条命令执行后Docker 会先在本地镜像库里找有没有叫 hello-world 的镜像没找到就自动去公共仓库拉取拉下来后创建容器并运行程序打印一段说明文字后退出。这个过程很多人跑完就完了但我建议你多看一眼输出里“Docker daemon”那几行字——它实际在告诉你镜像从哪里来、容器为什么能启动、以及当前用户权限是否正常。有一个细节容易被忽略docker run是一个组合命令它等于docker pull加docker create加docker start再加docker attach只是 Docker 帮你串起来了。这也是入门时最容易混淆的概念。分开执行时每一步的可观测性更强# 分开执行观察每步输出 docker pull hello-world docker create hello-world docker start -a 容器ID参数说明create 之后会返回一串容器 ID不会立刻运行start -a 表示附加到容器的标准输出上把程序打印的内容显示到当前终端。如果 start 不加 -a容器是在后台跑的你什么都看不到。这个习惯值得养成——出问题时把 run 拆开看能快速定位是拉取阶段挂了还是创建阶段挂了还是运行阶段挂了。2.3 网络环境差异导致的拉取慢问题初学者最常见的挫败感来源是 pull 镜像时卡住不动或超时。这不是 Docker 本身的问题而是网络环境到公共镜像仓库之间链路质量的客观差异。处理思路是配置 registry-mirrors让 Docker 从你所在网络能更快访问的镜像源拉取。# 编辑 Docker 守护进程配置文件 sudo vi /etc/docker/daemon.json{ registry-mirrors: [https://你的可用镜像源地址] }修改后必须重启守护进程才生效sudo systemctl daemon-reload sudo systemctl restart docker docker info | grep -A 1 Registry Mirrors注意docker info 里能看到镜像源配置是否加载成功。这里不推荐照抄网上的某个固定地址因为不同地区、不同运营商的访问速度差异很大最好的做法是问一下你所在团队的同事“你们配的什么源”那往往是经过实践验证的可用地址。另外有些定位完网络问题的人会走偏试图通过修改镜像源来解决所有拉取问题——镜像源只能改善镜像拉取链路容器运行时访问外部服务的网络问题那是另一回事别混为一谈。3. 镜像与容器理解 Docker 的两个核心对象3.1 镜像不是一个大文件是一摞只读层很多人第一次看到 docker images 列表时以为镜像就是一个压缩包式的完整文件。这个理解会导致后续很多困惑——为什么删掉容器后重新运行还是原来的状态为什么两个镜像能共享几百兆的空间Docker 的底层设计是分层文件系统镜像由一层层只读层叠加而成每一层对应 Dockerfile 里的一条指令。运行时Docker 在只读层之上再挂一个可写层所有写入操作都在可写层完成。容器删除时销毁的是可写层底层只读镜像层原封不动。# 查看镜像分层信息的经典命令 docker history 镜像名:标签 # 查看镜像在磁盘上的实际占用 docker system dfdocker history 输出里每一行代表一个镜像层能看到每层的大小和创建方式。docker system df 会列出一组数据镜像占了多少、容器占了多少、可回收的是哪些。如果发现系统盘空间紧张这个命令就是排查的入口。理解分层之后有几个操作习惯会自然改掉不要为了调试往容器里装一堆工具然后把容器 commit 成新镜像因为 commit 会把可写层整个拍进去产生大量冗余层。3.2 用 Dockerfile 构建自己的镜像最小可复现拉取现成镜像只是入门真正让 Docker 发挥价值的是把你自己的应用打包成镜像。一个最小但完整的 Dockerfile 长这样# 基础镜像尽量选官方且带具体版本号的 FROM python:3.11-slim # 设置工作目录后续命令都会在这个目录下执行 WORKDIR /app # 先拷贝依赖清单再装依赖这一步是为了利用构建缓存 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 再拷贝项目代码代码变动不会导致上面的层重新构建 COPY . . # 声明容器运行时监听的端口纯文档性质 EXPOSE 8000 # 指定容器启动时执行的命令 CMD [python, app.py]构建命令和运行命令分开写是常见的执行方式docker build -t myapp:v1.0 . docker run -d -p 8000:8000 myapp:v1.0参数说明build -t 指定镜像名和标签最后的 . 是构建上下文目录Docker 会把整个目录打包发送给守护进程用于构建。所以构建上下文里别放无关的大文件——例如 node_modules 或 .git 目录会让构建过程变得奇慢。我一般会配合 .dockerignore 文件把不需要的文件排除掉规则和 .gitignore 类似。COPY requirements.txt 放在 COPY . . 之前利用的是 Docker 的层缓存机制只要 requirements.txt 没变pip install 那一步就会直接命中缓存重建镜像能省掉大量重复安装时间。3.3 容器生命周期创建、启动、停止、删除容器生命周期管理是最容易踩坑的环节别想当然。下面的表把常用命令按状态整理清楚操作命令示例作用阶段运行新容器前台docker run -it ubuntu bash创建并进入容器运行新容器后台docker run -d nginx创建并在后台运行列出运行中容器docker ps查看活动容器列出所有容器docker ps -a查看包含已退出的容器停止容器docker stop 容器ID发送停止信号强制杀掉docker kill 容器ID立即终止删除容器docker rm 容器ID移除容器文件删除镜像docker rmi 镜像ID移除镜像及引用初始化参数的差异值得单独说。-it是两个参数连写-i 保持标准输入打开-t 分配一个伪终端。跑交互式程序比如 bash、调试脚本时这两个必须一起用少一个都会发现进不去命令行界面。而跑守护型服务比如 nginx、数据库时用-d后台运行启动后终端不会卡住。删除容器的顺序有讲究。我曾经因为直接 docker rm 一个运行中的容器被报错原因是我没先 stop。正确的习惯是stop 等容器优雅退出再 rm 清理。如果是调试完的临时容器可以加 -f 参数强制删除省一步但业务环境的容器别这么干——强制删除会跳过优雅退出流程有些服务可能没来得及把缓冲数据落盘就断了。4. 数据持久化与端口映射让容器真正可用4.1 挂载数据卷容器删了数据还在镜像的只读层和容器的可写层特性带来一个绕不开的问题容器一旦删除可写层里的数据就跟着没了。这对跑数据库、存日志、存上传文件的场景是致命的。解决方案是数据卷volume或绑定挂载bind mount。# 方式一匿名卷由 Docker 管理存储路径 docker run -d -v /var/lib/mysql mysql:8.0 # 方式二命名卷方便复用和备份 docker volume create mysql_data docker run -d -v mysql_data:/var/lib/mysql mysql:8.0 # 方式三绑定挂载直接把宿主机目录映射进容器 docker run -d -v /home/user/mysql_data:/var/lib/mysql mysql:8.0三种方式的差异值得拎清楚。匿名卷适合临时验证你不需要关心数据存在哪命名卷适合正式环境Docker 负责管理存储位置你只需要记住卷名绑定挂载适合需要直接查看或备份数据的场景因为数据就在你指定的目录里。但绑定挂载有一个权限坑宿主机目录的 UID/GID 会直接映射进容器。如果容器内进程以 root 运行写文件你会看到宿主机目录里冒出一堆 root 权限的文件后续清理都费劲。这个问题我会在避坑章节展开。查看容器挂载情况用 inspect 命令docker inspect 容器ID | grep -A 5 MountsMounts 字段里能看到 Source宿主机路径、Destination容器内路径和 Type卷或绑定挂载。多容器共享同一个卷也是常见做法两个容器用同一个卷名映射不同目录即可。注意多个容器同时写同一个文件会有并发问题这类问题不归 Docker 管得靠应用自己解决。4.2 端口映射把容器里的服务暴露出来容器默认处在隔离网络里宿主机访问不到容器内监听的端口需要显式做映射。命令格式是 -p 宿主机端口:容器端口# 把容器内 80 端口映射到宿主机 8080 docker run -d -p 8080:80 nginx # 只绑定到指定 IP避免暴露到所有网卡 docker run -d -p 127.0.0.1:8080:80 nginx # 随机分配宿主机端口适合不想管端口冲突的场景 docker run -d -P nginx参数说明-p 前面是宿主机侧后面是容器侧方向写反了会直接连不通。第一次跑这个命令的人最常见的错误就是记反顺序。 -P大写会把容器内所有 EXPOSE 的端口随机映射到宿主机高位端口配合 docker port 容器ID 可以查到实际映射关系。生产环境我倾向于显式写 -p 指定固定端口因为随机端口不利于防火墙规则和负载均衡配置。局域网内其他机器想访问容器服务时访问的是宿主机 IP 加映射端口不是容器 IP。容器 IP 是 Docker 内部网络的地址每次重建容器都可能变依赖容器 IP 做访问是不推荐的。另外注意只 bind 在 127.0.0.1 的端口只能本机访问跨机器访问必须绑定到宿主机实际网卡 IP 或 0.0.0.0。4.3 环境变量同一个镜像跑出不同配置镜像是构建时固定的但不同环境开发、测试、生产的配置不同。常见的做法不是打三个镜像而是一个镜像传不同的环境变量。Docker 内环境变量的机制如下# 在 docker run 时传入环境变量 docker run -d -e MYSQL_ROOT_PASSWORDmysecret -e MYSQL_DATABASEappdb mysql:8.0 # 从文件批量读取环境变量推荐避免敏感信息出现在命令行历史里 docker run -d --env-file ./env.list mysql:8.0env.list 文件每行一个 KEYvalue以 # 开头的行会被忽略# 环境变量文件示例 MYSQL_ROOT_PASSWORDmysecret MYSQL_DATABASEappdb MYSQL_USERappuser MYSQL_PASSWORDapppass注意.env 文件不要提交到版本仓库里里面通常有密码或密钥。维护多个环境时我一般会为每个环境准备一份 env 文件env.dev、env.test、env.prod启动脚本里按环境变量选择加载哪一份。这里有不少血泪经验曾经有人在命令行直接 -e 传密码shell history 里留了明文后来整个团队的凭据轮换了一轮。能用 --env-file 就别用 -e 传敏感信息。另外一个常见认知偏差是在容器内修改配置文件再 commit 成新镜像这种做法建议尽量避免。配置应该通过环境变量或挂载文件注入而不是固化进镜像。否则每改一次配置就多一个镜像版本镜像仓库会变得难以维护。5. 避坑指南入门阶段最常见的 6 个翻车现场5.1 容器时间不对日志时间戳全乱套现象容器内 date 显示 UTC 时间和宿主机差 8 小时应用日志排查问题时对不上时间线。原因基础镜像默认时区是 UTC容器不会自动继承宿主机的时区设置。解决# 方式一运行时挂载宿主机的时区文件 docker run -d -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro 你的镜像 # 方式二在 Dockerfile 里预设时区更干净 ENV TZAsia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime echo $TZ /etc/timezone推荐方式二镜像一旦打出来不管在哪台机器上跑时区都是一致的。方式一依赖宿主机环境换到 macOS 或 Windows 环境时表现不稳。5.2 容器内文件是 root 属主宿主机删不掉现象绑定挂载目录里出现一堆 root 属主文件当前用户想删删不掉只能 sudo。原因容器内进程以 root 运行写入挂载目录的文件属主映射到了宿主机 root。解决运行容器时显式指定用户# 指定以当前用户 UID 运行容器内进程 docker run -d --user $(id -u):$(id -g) 你的镜像但这里有一个新坑容器内进程如果不是 root可能没权限写容器内的某些目录例如 /var/log。所以更稳妥的做法是在 Dockerfile 里先用 USER 指令切换并提前把需要写的目录 chown 给该用户。这个问题的本质是 UID 映射理解不到位建议在项目里尽早约定运行用户是什么、哪些目录需要写权限、挂载目录权限如何规划。5.3 容器启动后立刻退出docker ps 看不到现象docker run 后容器状态一直显示 Exiteddocker logs 什么都没有或只有一行报错。原因容器内主进程退出容器就结束这是 Docker 的运行机制。很多服务型镜像需要前台运行不能像在宿主机上那样 daemon 化。解决先看日志定位真正原因docker logs 容器ID docker inspect 容器ID | grep -A 10 StateState 字段里包含 ExitCode 和 ErrorExitCode 非 0 说明主进程崩溃0 说明是正常退出只是进程没有保持前台运行。某些服务在 Dockerfile 里 CMD 写了启动脚本但不加前台参数就会直接退出。例如 nginx 需要 nginx -g daemon off;redis 需要 redis-server --daemonize no。5.4 docker run 时报端口被占用现象docker run 报 bind: address already in use换一个端口又好使了。原因宿主机端口已被其他进程或其他容器占用。解决# 查谁占了端口 sudo lsof -i :8080 # 或 sudo netstat -tlnp | grep 8080确认占用者后要么停掉冲突进程要么把映射端口改掉。但这里建议改映射端口而不是盲目停进程——线上环境的端口占用可能来自别的关键服务停掉代价太大。还有一个容易忽略的点Docker 网桥 IP 段和局域网冲突时也可能出现奇怪的通不了网的问题那是网络层的事和端口占用是两个排查方向。新手遇到端口冲突就去停进程不是好习惯。5.5 镜像越拉越多磁盘空间告急现象docker system df 显示可回收空间很大但宿主机磁盘就是要满。原因docker build 过程中的中间层镜像、悬空镜像dangling images、停止的容器、无用的匿名卷都会占空间不自动清理。解决# 清理悬空镜像没有标签且没有被容器引用的镜像 docker image prune -f # 清理停止的容器 docker container prune -f # 清理所有未使用的卷谨慎匿名卷里的数据会丢 docker volume prune -f # 一次性清理全部无用资源最彻底 docker system prune -a --volumesdocker system prune -a 会连没有被容器使用的镜像一起删下次再用就得重新拉取所以执行前确认一下是否有依赖旧镜像的环境。这里最危险的是 docker volume prune -f它只提示卷会被删除但不会逐卷确认是否还有用。我见过有人把还挂着旧版本数据的卷一次性清掉后悔药是没有的。加 --volumes 清理离线数据前先跑一遍不带 --volumes 的清理确认后再处理卷。5.6 容器内 curl 不通外网但宿主机没问题现象宿主机 curl 外网正常进容器后 curl 超时报 DNS 或连接相关错误。原因容器默认网络模式是 bridgeDNS 配置继承自宿主机的 /etc/resolv.conf如果宿主机用了特定的 DNS 配置或容器内没有对应的 DNS 工具就会出问题。解决分两层# 临时进入容器排查网络 docker run -it --rm 你的镜像 bash cat /etc/resolv.conf # 指定 DNS 启动容器 docker run -d --dns 8.8.8.8 你的镜像这里也有一个常见的坑基础镜像是 slim 或 alpine 版时可能没装 curl、ping 这些排查工具进容器后连最基本连通性测试都做不了。这类问题我一般先查 resolv.conf再查路由如果有 ip 命令思路和排查物理机网络一样。如果你确认是镜像精简导致工具缺失可以用宿主机的网络模式做对照测试确认是 DNS 问题还是容器网络隔离问题。6. 进阶验证用一个完整小项目把 Docker 串起来前面各章都是单点知识点最后用一个综合练习把镜像构建、容器编排、数据持久化、环境变量、日志查看全部串起来。我选一个最常见的场景一个 Flask 应用搭配 Redis 做缓存数据落盘到宿主机。不用 docker compose 之前完整命令是这样的# 第一步构建应用镜像 docker build -t flask-demo:v1 . # 第二步先启动 Redis因为它要被应用依赖 docker run -d --name redis-cache -v redisdata:/data redis:7-alpine # 第三步启动 Flask 应用链接 Redis映射端口 docker run -d --name flask-app \ -p 8080:5000 \ -e REDIS_HOSTredis-cache \ -v applogs:/app/logs \ flask-demo:v1然后验证整个链路是否正常# 看两个容器的运行状态 docker ps --filter nameredis --filter nameflask # 看应用日志 docker logs -f flask-app # 进入应用容器内部验证能否连通 Redis docker exec -it flask-app sh -c curl http://redis-cache:6379/hi验证通过后你会接触到一个 Docker 内置的魔力容器名就是可解析的 DNS 名称。在上面的命令里Flask 应用里连接 Redis 时直接写 redis-cache 这个主机名就能连上不用关心 Redis 容器每次启动后的 IP 是多少。这是因为同一台机器上的容器默认加入同一个 bridge 网络彼此可以通过容器名互相访问。这是 Docker 网络模型里最实用的一个机制。这个练习跑通之后建议你自己再变体测试一下停掉 Redis 容器应用是否还能启动但报连接错误删掉 Flask 容器重新跑一个数据卷和日志卷是否还在改一个环境变量重启容器配置是否生效这四种情况覆盖了入门阶段 80% 的操作面。我的一个习惯是每学一个新命令就把它的关键参数文档读一遍然后用 docker inspect 看这个命令实际改了什么——很多看似玄学的问题其实是某个参数没吃透。这套路径和方法帮了不少人希望帮到你。本文还有配套的精品资源点击获取